From e9d7d5d5f2a4942af7d9e92e9685a79229f9e7b8 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Wed, 11 May 2022 03:56:40 -0700 Subject: [PATCH 01/59] Implement quests --- .../command/commands/QuestCommand.java | 66 ++++++ .../java/emu/grasscutter/data/GameData.java | 6 + .../emu/grasscutter/data/ResourceLoader.java | 34 +++- .../grasscutter/data/custom/QuestConfig.java | 25 +++ .../data/custom/QuestConfigData.java | 104 ++++++++++ .../grasscutter/database/DatabaseHelper.java | 15 ++ .../grasscutter/database/DatabaseManager.java | 5 +- .../emu/grasscutter/game/player/Player.java | 38 +++- .../grasscutter/game/quest/GameMainQuest.java | 124 ++++++++++++ .../emu/grasscutter/game/quest/GameQuest.java | 188 ++++++++++++++++++ .../grasscutter/game/quest/QuestManager.java | 119 +++++++++++ .../game/quest/enums/LogicType.java | 23 +++ .../game/quest/enums/ParentQuestState.java | 18 ++ .../game/quest/enums/QuestCondType.java | 92 +++++++++ .../game/quest/enums/QuestExecType.java | 82 ++++++++ .../game/quest/enums/QuestGuideType.java | 17 ++ .../game/quest/enums/QuestShowType.java | 16 ++ .../game/quest/enums/QuestState.java | 19 ++ .../game/quest/enums/QuestType.java | 22 ++ .../game/quest/enums/ShowQuestGuideType.java | 17 ++ .../send/PacketFinishedParentQuestNotify.java | 22 ++ ...PacketFinishedParentQuestUpdateNotify.java | 19 ++ .../packet/send/PacketQuestListNotify.java | 23 +++ .../send/PacketQuestListUpdateNotify.java | 20 ++ .../send/PacketQuestProgressUpdateNotify.java | 30 +++ ...etServerCondMeetQuestListUpdateNotify.java | 37 ++++ src/main/resources/languages/en-US.json | 8 + 27 files changed, 1183 insertions(+), 6 deletions(-) create mode 100644 src/main/java/emu/grasscutter/command/commands/QuestCommand.java create mode 100644 src/main/java/emu/grasscutter/data/custom/QuestConfig.java create mode 100644 src/main/java/emu/grasscutter/data/custom/QuestConfigData.java create mode 100644 src/main/java/emu/grasscutter/game/quest/GameMainQuest.java create mode 100644 src/main/java/emu/grasscutter/game/quest/GameQuest.java create mode 100644 src/main/java/emu/grasscutter/game/quest/QuestManager.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/LogicType.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/ParentQuestState.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/QuestCondType.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/QuestExecType.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/QuestGuideType.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/QuestShowType.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/QuestState.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/QuestType.java create mode 100644 src/main/java/emu/grasscutter/game/quest/enums/ShowQuestGuideType.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestUpdateNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketQuestListNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketQuestListUpdateNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketQuestProgressUpdateNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java diff --git a/src/main/java/emu/grasscutter/command/commands/QuestCommand.java b/src/main/java/emu/grasscutter/command/commands/QuestCommand.java new file mode 100644 index 000000000..70fae0120 --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/QuestCommand.java @@ -0,0 +1,66 @@ +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.game.quest.GameQuest; + +import java.util.List; + +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "quest", usage = "quest [quest id]", permission = "player.quest", permissionTargeted = "player.quest.others", description = "commands.quest.description") +public final class QuestCommand implements CommandHandler { + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + if (targetPlayer == null) { + CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target")); + return; + } + + if (args.size() != 2) { + CommandHandler.sendMessage(sender, translate(sender, "commands.quest.usage")); + return; + } + + String cmd = args.get(0).toLowerCase(); + int questId; + + try { + questId = Integer.parseInt(args.get(1)); + } catch (Exception e) { + CommandHandler.sendMessage(sender, translate(sender, "commands.quest.invalid_id")); + return; + } + + switch (cmd) { + case "add" -> { + GameQuest quest = sender.getQuestManager().addQuest(questId); + + if (quest != null) { + CommandHandler.sendMessage(sender, translate(sender, "commands.quest.added", questId)); + return; + } + + CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found")); + } + case "finish" -> { + GameQuest quest = sender.getQuestManager().getQuestById(questId); + + if (quest == null) { + CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found")); + return; + } + + quest.finish(); + + CommandHandler.sendMessage(sender, translate(sender, "commands.quest.finished", questId)); + } + default -> { + CommandHandler.sendMessage(sender, translate(sender, "commands.quest.usage")); + } + } + } +} diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index ac2472192..2b40818e1 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -12,6 +12,7 @@ import emu.grasscutter.data.custom.AbilityEmbryoEntry; import emu.grasscutter.data.custom.AbilityModifier; import emu.grasscutter.data.custom.AbilityModifierEntry; import emu.grasscutter.data.custom.OpenConfigEntry; +import emu.grasscutter.data.custom.QuestConfig; import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.data.def.*; import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; @@ -27,6 +28,7 @@ public class GameData { private static final Map abilityModifiers = new HashMap<>(); private static final Map openConfigEntries = new HashMap<>(); private static final Map scenePointEntries = new HashMap<>(); + private static final Int2ObjectMap questConfigs = new Int2ObjectOpenHashMap<>(); // ExcelConfigs private static final Int2ObjectMap playerLevelDataMap = new Int2ObjectOpenHashMap<>(); @@ -122,6 +124,10 @@ public class GameData { return getScenePointEntries().get(sceneId + "_" + pointId); } + public static Int2ObjectMap getQuestConfigs() { + return questConfigs; + } + public static Int2ObjectMap getAvatarDataMap() { return avatarDataMap; } diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index c2708bd63..ae73de8ff 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -24,6 +24,9 @@ import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction; import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType; import emu.grasscutter.data.custom.AbilityModifierEntry; import emu.grasscutter.data.custom.OpenConfigEntry; +import emu.grasscutter.data.custom.QuestConfig; +import emu.grasscutter.data.custom.QuestConfigData; +import emu.grasscutter.data.custom.QuestConfigData.SubQuestConfigData; import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.game.world.SpawnDataEntry; import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry; @@ -57,8 +60,9 @@ public class ResourceLoader { loadResources(); // Process into depots GameDepot.load(); - // Load spawn data + // Load spawn data and quests loadSpawnData(); + loadQuests(); // Load scene points - must be done AFTER resources are loaded loadScenePoints(); // Custom - TODO move this somewhere else @@ -396,6 +400,34 @@ public class ResourceLoader { GameData.getOpenConfigEntries().put(entry.getName(), entry); } } + + private static void loadQuests() { + File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Quest/"); + + if (!folder.exists()) { + return; + } + + for (File file : folder.listFiles()) { + QuestConfigData mainQuest = null; + + try (FileReader fileReader = new FileReader(file)) { + mainQuest = Grasscutter.getGsonFactory().fromJson(fileReader, QuestConfigData.class); + } catch (Exception e) { + e.printStackTrace(); + continue; + } + + if (mainQuest.getSubQuests() != null) { + for (SubQuestConfigData subQuest : mainQuest.getSubQuests()) { + QuestConfig quest = new QuestConfig(mainQuest, subQuest); + GameData.getQuestConfigs().put(quest.getId(), quest); + } + } + } + + Grasscutter.getLogger().info("Loaded " + GameData.getQuestConfigs().size() + " Quest Configs"); + } // BinOutput configs diff --git a/src/main/java/emu/grasscutter/data/custom/QuestConfig.java b/src/main/java/emu/grasscutter/data/custom/QuestConfig.java new file mode 100644 index 000000000..8674ff7ab --- /dev/null +++ b/src/main/java/emu/grasscutter/data/custom/QuestConfig.java @@ -0,0 +1,25 @@ +package emu.grasscutter.data.custom; + +import emu.grasscutter.data.custom.QuestConfigData.SubQuestConfigData; + +public class QuestConfig { + private final QuestConfigData mainQuest; + private final SubQuestConfigData subQuest; + + public QuestConfig(QuestConfigData mainQuest, SubQuestConfigData subQuest) { + this.mainQuest = mainQuest; + this.subQuest = subQuest; + } + + public int getId() { + return subQuest.getSubId(); + } + + public QuestConfigData getMainQuest() { + return mainQuest; + } + + public SubQuestConfigData getSubQuest() { + return subQuest; + } +} diff --git a/src/main/java/emu/grasscutter/data/custom/QuestConfigData.java b/src/main/java/emu/grasscutter/data/custom/QuestConfigData.java new file mode 100644 index 000000000..4ba0ce47c --- /dev/null +++ b/src/main/java/emu/grasscutter/data/custom/QuestConfigData.java @@ -0,0 +1,104 @@ +package emu.grasscutter.data.custom; + +import emu.grasscutter.game.quest.enums.LogicType; +import emu.grasscutter.game.quest.enums.QuestCondType; +import emu.grasscutter.game.quest.enums.QuestType; + +public class QuestConfigData { + private int id; + private int series; + private QuestType type; + + private long titleTextMapHash; + private int[] suggestTrackMainQuestList; + private int[] rewardIdList; + + private SubQuestConfigData[] subQuests; + + public int getId() { + return id; + } + + public int getSeries() { + return series; + } + + public QuestType getType() { + return type; + } + + public long getTitleTextMapHash() { + return titleTextMapHash; + } + + public int[] getSuggestTrackMainQuestList() { + return suggestTrackMainQuestList; + } + + public int[] getRewardIdList() { + return rewardIdList; + } + + public SubQuestConfigData[] getSubQuests() { + return subQuests; + } + + public class SubQuestConfigData { + private int subId; + private int mainId; + + private LogicType acceptCondComb; + private QuestCondition[] acceptCond; + + private LogicType finishCondComb; + private QuestCondition[] finishCond; + + private LogicType failCondComb; + private QuestCondition[] failCond; + + public int getSubId() { + return subId; + } + + public int getMainId() { + return mainId; + } + + public LogicType getAcceptCondComb() { + return acceptCondComb; + } + + public QuestCondition[] getAcceptCond() { + return acceptCond; + } + + public LogicType getFinishCondComb() { + return finishCondComb; + } + + public QuestCondition[] getFinishCond() { + return finishCond; + } + + public LogicType getFailCondComb() { + return failCondComb; + } + + public QuestCondition[] getFailCond() { + return failCond; + } + } + + public class QuestCondition { + private QuestCondType type; + private int[] param; + + public QuestCondType getType() { + return type; + } + + public int[] getParam() { + return param; + } + } +} diff --git a/src/main/java/emu/grasscutter/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index 8f1de0bb9..fe931bdc3 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseHelper.java +++ b/src/main/java/emu/grasscutter/database/DatabaseHelper.java @@ -15,6 +15,7 @@ import emu.grasscutter.game.gacha.GachaRecord; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameMainQuest; import static com.mongodb.client.model.Filters.eq; @@ -111,6 +112,8 @@ public final class DatabaseHelper { DatabaseManager.getDatabase().getCollection("gachas").deleteMany(eq("ownerId", target.getPlayerUid())); // Delete GameItem.class data DatabaseManager.getDatabase().getCollection("items").deleteMany(eq("ownerId", target.getPlayerUid())); + // Delete GameMainQuest.class data + DatabaseManager.getDatabase().getCollection("quests").deleteMany(eq("ownerUid", target.getPlayerUid())); // Delete friendships. // Here, we need to make sure to not only delete the deleted account's friendships, @@ -260,4 +263,16 @@ public final class DatabaseHelper { DeleteResult result = DatabaseManager.getDatastore().delete(mail); return result.wasAcknowledged(); } + + public static List getAllQuests(Player player) { + return DatabaseManager.getDatastore().find(GameMainQuest.class).filter(Filters.eq("ownerUid", player.getUid())).stream().toList(); + } + + public static void saveQuest(GameMainQuest quest) { + DatabaseManager.getDatastore().save(quest); + } + + public static boolean deleteQuest(GameMainQuest quest) { + return DatabaseManager.getDatastore().delete(quest).wasAcknowledged(); + } } diff --git a/src/main/java/emu/grasscutter/database/DatabaseManager.java b/src/main/java/emu/grasscutter/database/DatabaseManager.java index 90ff17238..12bb444b8 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseManager.java +++ b/src/main/java/emu/grasscutter/database/DatabaseManager.java @@ -20,6 +20,8 @@ import emu.grasscutter.game.gacha.GachaRecord; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.mail.Mail; import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; public final class DatabaseManager { @@ -30,7 +32,8 @@ public final class DatabaseManager { private static Datastore dispatchDatastore; private static final Class[] mappedClasses = new Class[] { - DatabaseCounter.class, Account.class, Player.class, Avatar.class, GameItem.class, Friendship.class, GachaRecord.class, Mail.class + DatabaseCounter.class, Account.class, Player.class, Avatar.class, GameItem.class, Friendship.class, + GachaRecord.class, Mail.class, GameMainQuest.class }; public static Datastore getDatastore() { diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 477f974ea..e8c4cd61b 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -29,6 +29,9 @@ import emu.grasscutter.game.props.ActionReason; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.SceneType; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestManager; import emu.grasscutter.game.shop.ShopLimit; import emu.grasscutter.game.managers.MapMarkManager.*; import emu.grasscutter.game.tower.TowerManager; @@ -91,6 +94,7 @@ public class Player { @Transient private MailHandler mailHandler; @Transient private MessageHandler messageHandler; @Transient private AbilityManager abilityManager; + @Transient private QuestManager questManager; @Transient private SotSManager sotsManager; @@ -145,6 +149,7 @@ public class Player { this.mailHandler = new MailHandler(this); this.towerManager = new TowerManager(this); this.abilityManager = new AbilityManager(this); + this.setQuestManager(new QuestManager(this)); this.pos = new Position(); this.rotation = new Position(); this.properties = new HashMap<>(); @@ -409,6 +414,14 @@ public class Player { return towerManager; } + public QuestManager getQuestManager() { + return questManager; + } + + public void setQuestManager(QuestManager questManager) { + this.questManager = questManager; + } + public PlayerGachaInfo getGachaInfo() { return gachaInfo; } @@ -883,9 +896,7 @@ public class Player { } public void sendPacket(BasePacket packet) { - if (this.hasSentAvatarDataNotify) { - this.getSession().send(packet); - } + this.getSession().send(packet); } public OnlinePlayerInfo getOnlinePlayerInfo() { @@ -1118,7 +1129,23 @@ public class Player { this.getFriendsList().loadFromDatabase(); this.getMailHandler().loadFromDatabase(); + this.getQuestManager().loadFromDatabase(); + + // Quest - Commented out because a problem is caused if you log out while this quest is active + /* + if (getQuestManager().getMainQuestById(351) == null) { + GameQuest quest = getQuestManager().addQuest(35104); + if (quest != null) { + quest.finish(); + } + getQuestManager().addQuest(35101); + + this.setSceneId(3); + this.getPos().set(GameConstants.START_POSITION); + } + */ + // Create world World world = new World(this); world.addPlayer(this); @@ -1138,7 +1165,10 @@ public class Player { session.send(new PacketStoreWeightLimitNotify()); session.send(new PacketPlayerStoreNotify(this)); session.send(new PacketAvatarDataNotify(this)); - + session.send(new PacketFinishedParentQuestNotify(this)); + session.send(new PacketQuestListNotify(this)); + session.send(new PacketServerCondMeetQuestListUpdateNotify(this)); + getTodayMoonCard(); // The timer works at 0:0, some users log in after that, use this method to check if they have received a reward today or not. If not, send the reward. session.send(new PacketPlayerEnterSceneNotify(this)); // Enter game world diff --git a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java new file mode 100644 index 000000000..1ceda3356 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java @@ -0,0 +1,124 @@ +package emu.grasscutter.game.quest; + +import java.util.HashMap; +import java.util.Map; + +import org.bson.types.ObjectId; + +import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Id; +import dev.morphia.annotations.Indexed; +import dev.morphia.annotations.Transient; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.custom.QuestConfig; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.enums.ParentQuestState; +import emu.grasscutter.game.quest.enums.QuestState; +import emu.grasscutter.net.proto.ChildQuestOuterClass.ChildQuest; +import emu.grasscutter.net.proto.ParentQuestOuterClass.ParentQuest; +import emu.grasscutter.net.proto.QuestOuterClass.Quest; +import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify; +import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify; +import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify; +import emu.grasscutter.utils.Utils; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +@Entity(value = "quests", useDiscriminator = false) +public class GameMainQuest { + @Id private ObjectId id; + + @Indexed private int ownerUid; + @Transient private Player owner; + + private Map childQuests; + + private int parentQuestId; + private int[] questVars; + private ParentQuestState state; + private boolean isFinished; + + @Deprecated // Morphia only. Do not use. + public GameMainQuest() {} + + public GameMainQuest(Player player, int parentQuestId) { + this.owner = player; + this.ownerUid = player.getUid(); + this.parentQuestId = parentQuestId; + this.childQuests = new HashMap<>(); + this.questVars = new int[5]; + this.state = ParentQuestState.PARENT_QUEST_STATE_NONE; + } + + public int getParentQuestId() { + return parentQuestId; + } + + public int getOwnerUid() { + return ownerUid; + } + + public Player getOwner() { + return owner; + } + + public void setOwner(Player player) { + if (player.getUid() != this.getOwnerUid()) return; + this.owner = player; + } + + public Map getChildQuests() { + return childQuests; + } + + public GameQuest getChildQuestById(int id) { + return this.getChildQuests().get(id); + } + + public int[] getQuestVars() { + return questVars; + } + + public ParentQuestState getState() { + return state; + } + + public boolean isFinished() { + return isFinished; + } + + public void finish() { + this.isFinished = true; + this.state = ParentQuestState.PARENT_QUEST_STATE_FINISHED; + this.getOwner().getSession().send(new PacketFinishedParentQuestUpdateNotify(this)); + } + + public void save() { + DatabaseHelper.saveQuest(this); + } + + public ParentQuest toProto() { + ParentQuest.Builder proto = ParentQuest.newBuilder() + .setParentQuestId(getParentQuestId()) + .setIsFinished(isFinished()) + .setParentQuestState(getState().getValue()); + + for (GameQuest quest : this.getChildQuests().values()) { + ChildQuest childQuest = ChildQuest.newBuilder() + .setQuestId(quest.getQuestId()) + .setState(quest.getState().getValue()) + .build(); + + proto.addChildQuestList(childQuest); + } + + if (getQuestVars() != null) { + for (int i : getQuestVars()) { + proto.addQuestVar(i); + } + } + + return proto.build(); + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/GameQuest.java b/src/main/java/emu/grasscutter/game/quest/GameQuest.java new file mode 100644 index 000000000..53599830c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/GameQuest.java @@ -0,0 +1,188 @@ +package emu.grasscutter.game.quest; + +import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Transient; +import emu.grasscutter.data.custom.QuestConfig; +import emu.grasscutter.data.custom.QuestConfigData.SubQuestConfigData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.enums.QuestState; +import emu.grasscutter.net.proto.QuestOuterClass.Quest; +import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify; +import emu.grasscutter.utils.Utils; + +@Entity +public class GameQuest { + @Transient private GameMainQuest mainQuest; + @Transient private QuestConfig config; + + private int questId; + private int mainQuestId; + private QuestState state; + + private int startTime; + private int acceptTime; + private int finishTime; + + private int[] finishProgressList; + private int[] failProgressList; + + @Deprecated // Morphia only. Do not use. + public GameQuest() {} + + public GameQuest(GameMainQuest mainQuest, QuestConfig config) { + this.mainQuest = mainQuest; + this.questId = config.getId(); + this.mainQuestId = config.getMainQuest().getId(); + this.config = config; + this.acceptTime = Utils.getCurrentSeconds(); + this.startTime = this.acceptTime; + this.state = QuestState.QUEST_STATE_UNFINISHED; + + if (config.getSubQuest().getFinishCond() != null) { + this.finishProgressList = new int[config.getSubQuest().getFinishCond().length]; + } + + if (config.getSubQuest().getFailCond() != null) { + this.failProgressList = new int[config.getSubQuest().getFailCond().length]; + } + + this.mainQuest.getChildQuests().put(this.questId, this); + } + + public GameMainQuest getMainQuest() { + return mainQuest; + } + + public void setMainQuest(GameMainQuest mainQuest) { + this.mainQuest = mainQuest; + } + + public Player getOwner() { + return getMainQuest().getOwner(); + } + + public int getQuestId() { + return questId; + } + + public int getMainQuestId() { + return mainQuestId; + } + + public QuestConfig getConfig() { + return config; + } + + public void setConfig(QuestConfig config) { + if (this.getQuestId() != config.getId()) return; + this.config = config; + } + + public QuestState getState() { + return state; + } + + public void setState(QuestState state) { + this.state = state; + } + + public int getStartTime() { + return startTime; + } + + public void setStartTime(int startTime) { + this.startTime = startTime; + } + + public int getAcceptTime() { + return acceptTime; + } + + public void setAcceptTime(int acceptTime) { + this.acceptTime = acceptTime; + } + + public int getFinishTime() { + return finishTime; + } + + public void setFinishTime(int finishTime) { + this.finishTime = finishTime; + } + + public int[] getFinishProgressList() { + return finishProgressList; + } + + public void setFinishProgress(int index, int value) { + finishProgressList[index] = value; + } + + public int[] getFailProgressList() { + return failProgressList; + } + + public void setFailProgress(int index, int value) { + failProgressList[index] = value; + } + + public void finish() { + this.state = QuestState.QUEST_STATE_FINISHED; + this.finishTime = Utils.getCurrentSeconds(); + + if (this.getFinishProgressList() != null) { + for (int i = 0 ; i < getFinishProgressList().length; i++) { + getFinishProgressList()[i] = 1; + } + } + + this.getOwner().getSession().send(new PacketQuestProgressUpdateNotify(this)); + + // Finish main quest if all child quests are done + this.tryFinishMainQuest(); + this.save(); + } + + public boolean tryFinishMainQuest() { + try { + SubQuestConfigData subQuestData = getConfig().getMainQuest().getSubQuests()[getConfig().getMainQuest().getSubQuests().length - 1]; + + if (subQuestData.getSubId() == this.getQuestId()) { + getMainQuest().finish(); + return true; + } + } catch (Exception e) { + + } + + return false; + } + + public void save() { + getMainQuest().save(); + } + + public Quest toProto() { + Quest.Builder proto = Quest.newBuilder() + .setQuestId(this.getQuestId()) + .setState(this.getState().getValue()) + .setParentQuestId(this.getMainQuestId()) + .setStartTime(this.getStartTime()) + .setStartGameTime(438) + .setAcceptTime(this.getAcceptTime()); + + if (this.getFinishProgressList() != null) { + for (int i : this.getFinishProgressList()) { + proto.addFinishProgressList(i); + } + } + + if (this.getFailProgressList() != null) { + for (int i : this.getFailProgressList()) { + proto.addFailProgressList(i); + } + } + + return proto.build(); + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/QuestManager.java b/src/main/java/emu/grasscutter/game/quest/QuestManager.java new file mode 100644 index 000000000..e1c26704c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/QuestManager.java @@ -0,0 +1,119 @@ +package emu.grasscutter.game.quest; + +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.custom.QuestConfig; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.enums.QuestState; +import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify; +import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify; +import emu.grasscutter.server.packet.send.PacketServerCondMeetQuestListUpdateNotify; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +public class QuestManager { + private final Player player; + private final Int2ObjectMap quests; + + public QuestManager(Player player) { + this.player = player; + this.quests = new Int2ObjectOpenHashMap<>(); + } + + public Player getPlayer() { + return player; + } + + public Int2ObjectMap getQuests() { + return quests; + } + + public GameMainQuest getMainQuestById(int mainQuestId) { + return getQuests().get(mainQuestId); + } + + public GameQuest getQuestById(int questId) { + QuestConfig questConfig = GameData.getQuestConfigs().get(questId); + if (questConfig == null) { + return null; + } + + GameMainQuest mainQuest = getQuests().get(questConfig.getMainQuest().getId()); + + if (mainQuest == null) { + return null; + } + + return mainQuest.getChildQuests().get(questId); + } + + public void forEachQuest(Consumer callback) { + for (GameMainQuest mainQuest : getQuests().values()) { + for (GameQuest quest : mainQuest.getChildQuests().values()) { + callback.accept(quest); + } + } + } + + public GameMainQuest addMainQuest(QuestConfig questConfig) { + GameMainQuest mainQuest = new GameMainQuest(getPlayer(), questConfig.getMainQuest().getId()); + getQuests().put(mainQuest.getParentQuestId(), mainQuest); + + getPlayer().sendPacket(new PacketFinishedParentQuestUpdateNotify(mainQuest)); + + return mainQuest; + } + + public GameQuest addQuest(int questId) { + QuestConfig questConfig = GameData.getQuestConfigs().get(questId); + if (questConfig == null) { + return null; + } + + // Main quest + GameMainQuest mainQuest = this.getMainQuestById(questConfig.getMainQuest().getId()); + + // Create main quest if it doesnt exist + if (mainQuest == null) { + mainQuest = addMainQuest(questConfig); + } + + // Sub quest + GameQuest quest = mainQuest.getChildQuestById(questId); + + if (quest != null) { + return null; + } + + // Create + quest = new GameQuest(mainQuest, questConfig); + + // Save main quest + mainQuest.save(); + + // Send packet + getPlayer().sendPacket(new PacketServerCondMeetQuestListUpdateNotify(quest)); + getPlayer().sendPacket(new PacketQuestListUpdateNotify(quest)); + + return quest; + } + + public void loadFromDatabase() { + List quests = DatabaseHelper.getAllQuests(getPlayer()); + + for (GameMainQuest mainQuest : quests) { + mainQuest.setOwner(this.getPlayer()); + + for (GameQuest quest : mainQuest.getChildQuests().values()) { + quest.setMainQuest(mainQuest); + quest.setConfig(GameData.getQuestConfigs().get(quest.getQuestId())); + } + + this.getQuests().put(mainQuest.getParentQuestId(), mainQuest); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/LogicType.java b/src/main/java/emu/grasscutter/game/quest/enums/LogicType.java new file mode 100644 index 000000000..608ec9c28 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/LogicType.java @@ -0,0 +1,23 @@ +package emu.grasscutter.game.quest.enums; + +public enum LogicType { + LOGIC_NONE (0), + LOGIC_AND (1), + LOGIC_OR (2), + LOGIC_NOT (3), + LOGIC_A_AND_ETCOR (4), + LOGIC_A_AND_B_AND_ETCOR (5), + LOGIC_A_OR_ETCAND (6), + LOGIC_A_OR_B_OR_ETCAND (7), + LOGIC_A_AND_B_OR_ETCAND (8); + + private final int value; + + LogicType(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/ParentQuestState.java b/src/main/java/emu/grasscutter/game/quest/enums/ParentQuestState.java new file mode 100644 index 000000000..6c7805f8d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/ParentQuestState.java @@ -0,0 +1,18 @@ +package emu.grasscutter.game.quest.enums; + +public enum ParentQuestState { + PARENT_QUEST_STATE_NONE (0), + PARENT_QUEST_STATE_FINISHED (1), + PARENT_QUEST_STATE_FAILED (2), + PARENT_QUEST_STATE_CANCELED (3); + + private final int value; + + ParentQuestState(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestCondType.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestCondType.java new file mode 100644 index 000000000..42db14f2d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestCondType.java @@ -0,0 +1,92 @@ +package emu.grasscutter.game.quest.enums; + +public enum QuestCondType { + QUEST_COND_NONE (0), + QUEST_COND_STATE_EQUAL (1), + QUEST_COND_STATE_NOT_EQUAL (2), + QUEST_COND_PACK_HAVE_ITEM (3), + QUEST_COND_AVATAR_ELEMENT_EQUAL (4), + QUEST_COND_AVATAR_ELEMENT_NOT_EQUAL (5), + QUEST_COND_AVATAR_CAN_CHANGE_ELEMENT (6), + QUEST_COND_CITY_LEVEL_EQUAL_GREATER (7), + QUEST_COND_ITEM_NUM_LESS_THAN (8), + QUEST_COND_DAILY_TASK_START (9), + QUEST_COND_OPEN_STATE_EQUAL (10), + QUEST_COND_DAILY_TASK_OPEN (11), + QUEST_COND_DAILY_TASK_REWARD_CAN_GET (12), + QUEST_COND_DAILY_TASK_REWARD_RECEIVED (13), + QUEST_COND_PLAYER_LEVEL_REWARD_CAN_GET (14), + QUEST_COND_EXPLORATION_REWARD_CAN_GET (15), + QUEST_COND_IS_WORLD_OWNER (16), + QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER (17), + QUEST_COND_SCENE_AREA_UNLOCKED (18), + QUEST_COND_ITEM_GIVING_ACTIVED (19), + QUEST_COND_ITEM_GIVING_FINISHED (20), + QUEST_COND_IS_DAYTIME (21), + QUEST_COND_CURRENT_AVATAR (22), + QUEST_COND_CURRENT_AREA (23), + QUEST_COND_QUEST_VAR_EQUAL (24), + QUEST_COND_QUEST_VAR_GREATER (25), + QUEST_COND_QUEST_VAR_LESS (26), + QUEST_COND_FORGE_HAVE_FINISH (27), + QUEST_COND_DAILY_TASK_IN_PROGRESS (28), + QUEST_COND_DAILY_TASK_FINISHED (29), + QUEST_COND_ACTIVITY_COND (30), + QUEST_COND_ACTIVITY_OPEN (31), + QUEST_COND_DAILY_TASK_VAR_GT (32), + QUEST_COND_DAILY_TASK_VAR_EQ (33), + QUEST_COND_DAILY_TASK_VAR_LT (34), + QUEST_COND_BARGAIN_ITEM_GT (35), + QUEST_COND_BARGAIN_ITEM_EQ (36), + QUEST_COND_BARGAIN_ITEM_LT (37), + QUEST_COND_COMPLETE_TALK (38), + QUEST_COND_NOT_HAVE_BLOSSOM_TALK (39), + QUEST_COND_IS_CUR_BLOSSOM_TALK (40), + QUEST_COND_QUEST_NOT_RECEIVE (41), + QUEST_COND_QUEST_SERVER_COND_VALID (42), + QUEST_COND_ACTIVITY_CLIENT_COND (43), + QUEST_COND_QUEST_GLOBAL_VAR_EQUAL (44), + QUEST_COND_QUEST_GLOBAL_VAR_GREATER (45), + QUEST_COND_QUEST_GLOBAL_VAR_LESS (46), + QUEST_COND_PERSONAL_LINE_UNLOCK (47), + QUEST_COND_CITY_REPUTATION_REQUEST (48), + QUEST_COND_MAIN_COOP_START (49), + QUEST_COND_MAIN_COOP_ENTER_SAVE_POINT (50), + QUEST_COND_CITY_REPUTATION_LEVEL (51), + QUEST_COND_CITY_REPUTATION_UNLOCK (52), + QUEST_COND_LUA_NOTIFY (53), + QUEST_COND_CUR_CLIMATE (54), + QUEST_COND_ACTIVITY_END (55), + QUEST_COND_COOP_POINT_RUNNING (56), + QUEST_COND_GADGET_TALK_STATE_EQUAL (57), + QUEST_COND_AVATAR_FETTER_GT (58), + QUEST_COND_AVATAR_FETTER_EQ (59), + QUEST_COND_AVATAR_FETTER_LT (60), + QUEST_COND_NEW_HOMEWORLD_MOUDLE_UNLOCK (61), + QUEST_COND_NEW_HOMEWORLD_LEVEL_REWARD (62), + QUEST_COND_NEW_HOMEWORLD_MAKE_FINISH (63), + QUEST_COND_HOMEWORLD_NPC_EVENT (64), + QUEST_COND_TIME_VAR_GT_EQ (65), + QUEST_COND_TIME_VAR_PASS_DAY (66), + QUEST_COND_HOMEWORLD_NPC_NEW_TALK (67), + QUEST_COND_PLAYER_CHOOSE_MALE (68), + QUEST_COND_HISTORY_GOT_ANY_ITEM (69), + QUEST_COND_LEARNED_RECIPE (70), + QUEST_COND_LUNARITE_REGION_UNLOCKED (71), + QUEST_COND_LUNARITE_HAS_REGION_HINT_COUNT (72), + QUEST_COND_LUNARITE_COLLECT_FINISH (73), + QUEST_COND_LUNARITE_MARK_ALL_FINISH (74), + QUEST_COND_NEW_HOMEWORLD_SHOP_ITEM (75), + QUEST_COND_SCENE_POINT_UNLOCK (76), + QUEST_COND_SCENE_LEVEL_TAG_EQ (77); + + private final int value; + + QuestCondType(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestExecType.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestExecType.java new file mode 100644 index 000000000..4f3c2557c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestExecType.java @@ -0,0 +1,82 @@ +package emu.grasscutter.game.quest.enums; + +public enum QuestExecType { + QUEST_EXEC_NONE (0), + QUEST_EXEC_DEL_PACK_ITEM (1), + QUEST_EXEC_UNLOCK_POINT (2), + QUEST_EXEC_UNLOCK_AREA (3), + QUEST_EXEC_UNLOCK_FORCE (4), + QUEST_EXEC_LOCK_FORCE (5), + QUEST_EXEC_CHANGE_AVATAR_ELEMET (6), + QUEST_EXEC_REFRESH_GROUP_MONSTER (7), + QUEST_EXEC_SET_IS_FLYABLE (8), + QUEST_EXEC_SET_IS_WEATHER_LOCKED (9), + QUEST_EXEC_SET_IS_GAME_TIME_LOCKED (10), + QUEST_EXEC_SET_IS_TRANSFERABLE (11), + QUEST_EXEC_GRANT_TRIAL_AVATAR (12), + QUEST_EXEC_OPEN_BORED (13), + QUEST_EXEC_ROLLBACK_QUEST (14), + QUEST_EXEC_NOTIFY_GROUP_LUA (15), + QUEST_EXEC_SET_OPEN_STATE (16), + QUEST_EXEC_LOCK_POINT (17), + QUEST_EXEC_DEL_PACK_ITEM_BATCH (18), + QUEST_EXEC_REFRESH_GROUP_SUITE (19), + QUEST_EXEC_REMOVE_TRIAL_AVATAR (20), + QUEST_EXEC_SET_GAME_TIME (21), + QUEST_EXEC_SET_WEATHER_GADGET (22), + QUEST_EXEC_ADD_QUEST_PROGRESS (23), + QUEST_EXEC_NOTIFY_DAILY_TASK (24), + QUEST_EXEC_CREATE_PATTERN_GROUP (25), + QUEST_EXEC_REMOVE_PATTERN_GROUP (26), + QUEST_EXEC_REFRESH_GROUP_SUITE_RANDOM (27), + QUEST_EXEC_ACTIVE_ITEM_GIVING (28), + QUEST_EXEC_DEL_ALL_SPECIFIC_PACK_ITEM (29), + QUEST_EXEC_ROLLBACK_PARENT_QUEST (30), + QUEST_EXEC_LOCK_AVATAR_TEAM (31), + QUEST_EXEC_UNLOCK_AVATAR_TEAM (32), + QUEST_EXEC_UPDATE_PARENT_QUEST_REWARD_INDEX (33), + QUEST_EXEC_SET_DAILY_TASK_VAR (34), + QUEST_EXEC_INC_DAILY_TASK_VAR (35), + QUEST_EXEC_DEC_DAILY_TASK_VAR (36), + QUEST_EXEC_ACTIVE_ACTIVITY_COND_STATE (37), + QUEST_EXEC_INACTIVE_ACTIVITY_COND_STATE (38), + QUEST_EXEC_ADD_CUR_AVATAR_ENERGY (39), + QUEST_EXEC_START_BARGAIN (41), + QUEST_EXEC_STOP_BARGAIN (42), + QUEST_EXEC_SET_QUEST_GLOBAL_VAR (43), + QUEST_EXEC_INC_QUEST_GLOBAL_VAR (44), + QUEST_EXEC_DEC_QUEST_GLOBAL_VAR (45), + QUEST_EXEC_REGISTER_DYNAMIC_GROUP (46), + QUEST_EXEC_UNREGISTER_DYNAMIC_GROUP (47), + QUEST_EXEC_SET_QUEST_VAR (48), + QUEST_EXEC_INC_QUEST_VAR (49), + QUEST_EXEC_DEC_QUEST_VAR (50), + QUEST_EXEC_RANDOM_QUEST_VAR (51), + QUEST_EXEC_ACTIVATE_SCANNING_PIC (52), + QUEST_EXEC_RELOAD_SCENE_TAG (53), + QUEST_EXEC_REGISTER_DYNAMIC_GROUP_ONLY (54), + QUEST_EXEC_CHANGE_SKILL_DEPOT (55), + QUEST_EXEC_ADD_SCENE_TAG (56), + QUEST_EXEC_DEL_SCENE_TAG (57), + QUEST_EXEC_INIT_TIME_VAR (58), + QUEST_EXEC_CLEAR_TIME_VAR (59), + QUEST_EXEC_MODIFY_CLIMATE_AREA (60), + QUEST_EXEC_GRANT_TRIAL_AVATAR_AND_LOCK_TEAM (61), + QUEST_EXEC_CHANGE_MAP_AREA_STATE (62), + QUEST_EXEC_DEACTIVE_ITEM_GIVING (63), + QUEST_EXEC_CHANGE_SCENE_LEVEL_TAG (64), + QUEST_EXEC_UNLOCK_PLAYER_WORLD_SCENE (65), + QUEST_EXEC_LOCK_PLAYER_WORLD_SCENE (66), + QUEST_EXEC_FAIL_MAINCOOP (67), + QUEST_EXEC_MODIFY_WEATHER_AREA (68); + + private final int value; + + QuestExecType(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestGuideType.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestGuideType.java new file mode 100644 index 000000000..45915c6b7 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestGuideType.java @@ -0,0 +1,17 @@ +package emu.grasscutter.game.quest.enums; + +public enum QuestGuideType { + QUEST_GUIDE_NONE (0), + QUEST_GUIDE_LOCATION (1), + QUEST_GUIDE_NPC (2); + + private final int value; + + QuestGuideType(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestShowType.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestShowType.java new file mode 100644 index 000000000..014c1ee06 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestShowType.java @@ -0,0 +1,16 @@ +package emu.grasscutter.game.quest.enums; + +public enum QuestShowType { + QUEST_SHOW (0), + QUEST_HIDDEN (1); + + private final int value; + + QuestShowType(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestState.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestState.java new file mode 100644 index 000000000..d258a2582 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestState.java @@ -0,0 +1,19 @@ +package emu.grasscutter.game.quest.enums; + +public enum QuestState { + QUEST_STATE_NONE (0), + QUEST_STATE_UNSTARTED (1), + QUEST_STATE_UNFINISHED (2), + QUEST_STATE_FINISHED (3), + QUEST_STATE_FAILED (4); + + private final int value; + + QuestState(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestType.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestType.java new file mode 100644 index 000000000..fbbac2ae0 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestType.java @@ -0,0 +1,22 @@ +package emu.grasscutter.game.quest.enums; + +public enum QuestType { + AQ (0), + FQ (1), + LQ (2), + EQ (3), + DQ (4), + IQ (5), + VQ (6), + WQ (7); + + private final int value; + + QuestType(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/ShowQuestGuideType.java b/src/main/java/emu/grasscutter/game/quest/enums/ShowQuestGuideType.java new file mode 100644 index 000000000..d4e985592 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/ShowQuestGuideType.java @@ -0,0 +1,17 @@ +package emu.grasscutter.game.quest.enums; + +public enum ShowQuestGuideType { + QUEST_GUIDE_ITEM_ENABLE (0), + QUEST_GUIDE_ITEM_DISABLE (1), + QUEST_GUIDE_ITEM_MOVE_HIDE (2); + + private final int value; + + ShowQuestGuideType(int id) { + this.value = id; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java new file mode 100644 index 000000000..7d64da48f --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java @@ -0,0 +1,22 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.FinishedParentQuestNotifyOuterClass.FinishedParentQuestNotify; + +public class PacketFinishedParentQuestNotify extends BasePacket { + + public PacketFinishedParentQuestNotify(Player player) { + super(PacketOpcodes.FinishedParentQuestNotify, true); + + FinishedParentQuestNotify.Builder proto = FinishedParentQuestNotify.newBuilder(); + + for (GameMainQuest mainQuest : player.getQuestManager().getQuests().values()) { + proto.addParentQuestList(mainQuest.toProto()); + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestUpdateNotify.java new file mode 100644 index 000000000..68eab7222 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestUpdateNotify.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.FinishedParentQuestUpdateNotifyOuterClass.FinishedParentQuestUpdateNotify; + +public class PacketFinishedParentQuestUpdateNotify extends BasePacket { + + public PacketFinishedParentQuestUpdateNotify(GameMainQuest quest) { + super(PacketOpcodes.FinishedParentQuestUpdateNotify); + + FinishedParentQuestUpdateNotify proto = FinishedParentQuestUpdateNotify.newBuilder() + .addParentQuestList(quest.toProto()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListNotify.java new file mode 100644 index 000000000..ccf0d765a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListNotify.java @@ -0,0 +1,23 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.QuestManager; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.QuestListNotifyOuterClass.QuestListNotify; + +public class PacketQuestListNotify extends BasePacket { + + public PacketQuestListNotify(Player player) { + super(PacketOpcodes.QuestListNotify, true); + + QuestListNotify.Builder proto = QuestListNotify.newBuilder(); + + player.getQuestManager().forEachQuest(quest -> { + proto.addQuestList(quest.toProto()); + }); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListUpdateNotify.java new file mode 100644 index 000000000..adc0767a8 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListUpdateNotify.java @@ -0,0 +1,20 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.QuestListUpdateNotifyOuterClass.QuestListUpdateNotify; + +public class PacketQuestListUpdateNotify extends BasePacket { + + public PacketQuestListUpdateNotify(GameQuest quest) { + super(PacketOpcodes.QuestListUpdateNotify); + + QuestListUpdateNotify proto = QuestListUpdateNotify.newBuilder() + .addQuestList(quest.toProto()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketQuestProgressUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestProgressUpdateNotify.java new file mode 100644 index 000000000..76ee56316 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestProgressUpdateNotify.java @@ -0,0 +1,30 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.QuestProgressUpdateNotifyOuterClass.QuestProgressUpdateNotify; + +public class PacketQuestProgressUpdateNotify extends BasePacket { + + public PacketQuestProgressUpdateNotify(GameQuest quest) { + super(PacketOpcodes.QuestProgressUpdateNotify); + + QuestProgressUpdateNotify.Builder proto = QuestProgressUpdateNotify.newBuilder().setQuestId(quest.getQuestId()); + + if (quest.getFinishProgressList() != null) { + for (int i : quest.getFinishProgressList()) { + proto.addFinishProgressList(i); + } + } + + if (quest.getFailProgressList() != null) { + for (int i : quest.getFailProgressList()) { + proto.addFailProgressList(i); + } + } + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java new file mode 100644 index 000000000..b2ea3d577 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java @@ -0,0 +1,37 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.custom.QuestConfig; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.ServerCondMeetQuestListUpdateNotifyOuterClass.ServerCondMeetQuestListUpdateNotify; + +public class PacketServerCondMeetQuestListUpdateNotify extends BasePacket { + + public PacketServerCondMeetQuestListUpdateNotify(Player player) { + super(PacketOpcodes.ServerCondMeetQuestListUpdateNotify); + + ServerCondMeetQuestListUpdateNotify.Builder proto = ServerCondMeetQuestListUpdateNotify.newBuilder(); + + player.getQuestManager().forEachQuest(quest -> { + if (quest.getState().getValue() <= 2) { + proto.addAddQuestIdList(quest.getQuestId()); + } + }); + + this.setData(proto); + } + + public PacketServerCondMeetQuestListUpdateNotify(GameQuest quest) { + super(PacketOpcodes.ServerCondMeetQuestListUpdateNotify); + + ServerCondMeetQuestListUpdateNotify proto = ServerCondMeetQuestListUpdateNotify.newBuilder() + .addAddQuestIdList(quest.getQuestId()) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 893f490af..107a12d71 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -210,6 +210,14 @@ "success": "Coordinates: %s, %s, %s\nScene id: %s", "description": "Get coordinates." }, + "quest": { + "description": "Add or finish quests", + "usage": "quest [quest id]", + "added": "Quest %s added", + "finished": "Finished quest %s", + "not_found": "Quest not found", + "invalid_id": "Invalid quest id" + }, "reload": { "reload_start": "Reloading config.", "reload_done": "Reload complete.", From dfd8fcb250a5c456dc79affbeab1e3442300788a Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Wed, 11 May 2022 04:01:38 -0700 Subject: [PATCH 02/59] Fix build error from merge --- src/main/java/emu/grasscutter/data/ResourceLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 92fda7bb1..5c2ac1ee6 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -400,7 +400,7 @@ public class ResourceLoader { } private static void loadQuests() { - File folder = new File(Grasscutter.getConfig().RESOURCE_FOLDER + "BinOutput/Quest/"); + File folder = new File(RESOURCE("BinOutput/Quest/")); if (!folder.exists()) { return; From 9ed1bb9b940d98cbba73528bc3e0ea02dcafedd0 Mon Sep 17 00:00:00 2001 From: Secretboy-SMR Date: Wed, 11 May 2022 21:14:07 +0800 Subject: [PATCH 03/59] It will use the english as default rather than tell you the value is not exist if there's no translation for currently language --- src/main/java/emu/grasscutter/utils/Language.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/utils/Language.java b/src/main/java/emu/grasscutter/utils/Language.java index 3789f594a..c343e949e 100644 --- a/src/main/java/emu/grasscutter/utils/Language.java +++ b/src/main/java/emu/grasscutter/utils/Language.java @@ -160,7 +160,9 @@ public final class Language { JsonObject object = this.languageData; int index = 0; - String result = "This value does not exist. Please report this to the Discord: " + key; + String valueNotFoundPattern = "This value does not exist. Please report this to the Discord: "; + String result = valueNotFoundPattern + key; + boolean isValueFound = false; while (true) { if(index == keys.length) break; @@ -171,10 +173,18 @@ public final class Language { if(element.isJsonObject()) object = element.getAsJsonObject(); else { + isValueFound = true; result = element.getAsString(); break; } } else break; } + + if (!isValueFound && !languageCode.equals("en-US")) { + var englishValue = Grasscutter.getLanguage("en-US").get(key); + if (!englishValue.contains(valueNotFoundPattern)) { + result += "\nhere is english version:\n" + englishValue; + } + } this.cachedTranslations.put(key, result); return result; } From c932f9c7e5389868c958456f2cdb33ddf3d6382b Mon Sep 17 00:00:00 2001 From: Benjamin Elsdon Date: Wed, 11 May 2022 20:35:27 +0800 Subject: [PATCH 04/59] Add verifyUser to AuthenticationHandler --- .../dispatch/authentication/AuthenticationHandler.java | 7 +++++++ .../authentication/DefaultAuthenticationHandler.java | 6 ++++++ src/main/resources/languages/en-US.json | 3 +++ 3 files changed, 16 insertions(+) diff --git a/src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java b/src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java index 92a2961ea..e644a9f1d 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java +++ b/src/main/java/emu/grasscutter/server/dispatch/authentication/AuthenticationHandler.java @@ -12,5 +12,12 @@ public interface AuthenticationHandler { void handleRegister(Request req, Response res); void handleChangePassword(Request req, Response res); + /** + * Other plugins may need to verify a user's identity using details from handleLogin() + * @param details The user's unique one-time token that needs to be verified + * @return If the verification was successful + */ + boolean verifyUser(String details); + LoginResultJson handleGameLogin(Request req, LoginAccountRequestJson requestData); } diff --git a/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java b/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java index 67b3d4023..122e04ff6 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java +++ b/src/main/java/emu/grasscutter/server/dispatch/authentication/DefaultAuthenticationHandler.java @@ -28,6 +28,12 @@ public class DefaultAuthenticationHandler implements AuthenticationHandler { res.send("Authentication is not available with the default authentication method"); } + @Override + public boolean verifyUser(String details) { + Grasscutter.getLogger().info(translate("dispatch.authentication.default_unable_to_verify")); + return false; + } + @Override public LoginResultJson handleGameLogin(Request req, LoginAccountRequestJson requestData) { LoginResultJson responseData = new LoginResultJson(); diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index c9c3c0c70..2b392b682 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -16,6 +16,9 @@ "no_keystore_error": "[Dispatch] No SSL cert found! Falling back to HTTP server.", "default_password": "[Dispatch] The default keystore password was loaded successfully. Please consider setting the password to 123456 in config.json." }, + "authentication": { + "default_unable_to_verify": "[Authentication] Something called the verifyUser method which is unavailable in the default authentication handler" + }, "no_commands_error": "Commands are not supported in dispatch only mode.", "unhandled_request_error": "[Dispatch] Potential unhandled %s request: %s", "account": { From 99de46e2618a23b9012184a5c431e0caf964946a Mon Sep 17 00:00:00 2001 From: tester233 <105267106+tester233@users.noreply.github.com> Date: Wed, 11 May 2022 18:25:01 +0800 Subject: [PATCH 05/59] Improve text & remove extra punctuation --- src/main/resources/languages/zh-CN.json | 100 ++++++++++++------------ 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 2f9663f4f..01572a20b 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -55,7 +55,7 @@ "permission_error": "哼哼哼!你没有执行此命令的权限!请联系服务器管理员解决!", "console_execute_error": "此命令只能在服务器控制台执行呐~", "player_execute_error": "此命令只能在游戏内执行哦~", - "command_exist_error": "这条命令……好像找不到呢?。", + "command_exist_error": "这条命令...好像找不到呢?", "no_description_specified": "没有指定说明", "invalid": { "amount": "无效的数量。", @@ -97,19 +97,19 @@ "delete": "账号已删除。", "no_account": "账号不存在。", "command_usage": "用法:account <用户名> [uid]", - "description": "创建或删除账号。" + "description": "创建或删除账号" }, "broadcast": { "command_usage": "用法:broadcast <消息>", "message_sent": "公告已发送。", - "description": "向所有玩家发送公告。" + "description": "向所有玩家发送公告" }, "changescene": { "usage": "用法:changescene <场景ID>", "already_in_scene": "你已经在这个场景中了。", "success": "已切换至场景 %s。", "exists_error": "此场景不存在。", - "description": "切换指定场景。" + "description": "切换指定场景" }, "clear": { "command_usage": "用法:clear \nall: 所有, wp: 武器, art: 圣遗物, mat: 材料", @@ -120,32 +120,32 @@ "displays": "已清空 %s 的屏幕。", "virtuals": "已清除 %s 的所有货币和经验值。", "everything": "已清除 %s 的所有物品。", - "description": "从你的背包中删除所有未装备且已解锁的物品,包括稀有物品。" + "description": "从你的背包中删除所有未装备且已解锁的物品,包括稀有物品" }, "coop": { "usage": "用法:coop <玩家ID> <目标玩家ID>", - "success": "已强制传送 %s 到 %s 的世界", - "description": "强制传送指定用户到他人的世界。" + "success": "已强制传送 %s 到 %s 的世界。", + "description": "强制传送指定用户到他人的世界" }, "enter_dungeon": { "usage": "用法:enterdungeon <秘境ID>", - "changed": "已进入秘境 %s", + "changed": "已进入秘境 %s。", "not_found_error": "此秘境不存在。", "in_dungeon_error": "你已经在秘境中了。", - "description": "进入指定秘境。" + "description": "进入指定秘境" }, "giveAll": { "usage": "用法:giveall [玩家] [数量]", "started": "正在给予全部物品...", "success": "已给予 %s 全部物品。", "invalid_amount_or_playerId": "无效的数量/玩家ID。", - "description": "给予所有物品。" + "description": "给予所有物品" }, "giveArtifact": { "usage": "用法:giveart|gart [玩家] <圣遗物ID> <主词条ID> [<副词条ID>[,<强化次数>]]... [等级]", "id_error": "无效的圣遗物ID。", "success": "已将 %s 给予 %s。", - "description": "给予指定圣遗物。" + "description": "给予指定圣遗物" }, "giveChar": { "usage": "用法:givechar <玩家> <角色ID|角色名> [数量]", @@ -153,50 +153,50 @@ "invalid_avatar_id": "无效的角色ID。", "invalid_avatar_level": "无效的角色等级。", "invalid_avatar_or_player_id": "无效的角色ID/玩家ID。", - "description": "给予指定角色。" + "description": "给予指定角色" }, "give": { "usage": "用法:give <玩家> <物品ID|物品名> [数量] [等级] [精炼等级]", "refinement_only_applicable_weapons": "只有武器可以设置精炼等级。", "refinement_must_between_1_and_5": "精炼等级必须在 1 到 5 之间。", "given": "已将 %s 个 %s 给予 %s。", - "given_with_level_and_refinement": "已将 %s (等级 %s, 精炼 %s) %s 个给予 %s", - "given_level": "已将 %s (等级 %s) %s 个给予 %s", - "description": "给予指定物品。" + "given_with_level_and_refinement": "已将 %s (等级 %s, 精炼 %s) %s 个给予 %s。", + "given_level": "已将 %s (等级 %s) %s 个给予 %s。", + "description": "给予指定物品" }, "godmode": { - "success": "%s 的无敌模式已被设置为 %s。", - "description": "防止你受到伤害。" + "success": "%s 的上帝模式已被设置为 %s。", + "description": "防止你受到伤害" }, "heal": { "success": "已经治疗所有角色。", - "description": "治疗当前队伍的角色。" + "description": "治疗当前队伍的角色" }, "kick": { - "player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出", - "server_kick_player": "正在踢出玩家 [%s:%s]", - "description": "从服务器内踢出指定玩家。" + "player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出。", + "server_kick_player": "正在踢出玩家 [%s:%s]...", + "description": "从服务器内踢出指定玩家" }, "kill": { "usage": "用法:killall [玩家UID] [场景ID]", - "scene_not_found_in_player_world": "未在玩家世界中找到此场景", + "scene_not_found_in_player_world": "未在玩家世界中找到此场景。", "kill_monsters_in_scene": "已杀死场景 %s 中的 %s 个怪物。", - "description": "杀死所有怪物。" + "description": "杀死所有怪物" }, "killCharacter": { "usage": "用法:/killcharacter [玩家ID]", "success": "已杀死 %s 当前角色。", - "description": "杀死当前角色。" + "description": "杀死当前角色" }, "language": { "current_language": "当前语言是: %s", "language_changed": "语言切换至: %s", "language_not_found": "目前服务端没有这种语言: %s", - "description": "显示或切换当前语言。" + "description": "显示或切换当前语言" }, "list": { "success": "目前在线人数:%s", - "description": "查看所有玩家。" + "description": "查看所有玩家" }, "permission": { "usage": "用法:permission <用户名> <权限>", @@ -205,25 +205,25 @@ "remove": "权限已移除。", "not_have_error": "此玩家未拥有权限!", "account_error": "账号不存在。", - "description": "添加或移除指定玩家的权限。" + "description": "添加或移除指定玩家的权限" }, "position": { "success": "坐标:%s, %s, %s\n场景ID:%s", - "description": "获取所在位置。" + "description": "获取所在位置" }, "reload": { "reload_start": "正在重载配置文件和数据。", "reload_done": "重载完成。", - "description": "重载配置文件和数据。" + "description": "重载配置文件和数据" }, "resetConst": { "reset_all": "重置所有角色的命座。", "success": "已重置 %s 的命座,重新登录后生效。", - "description": "重置当前角色的命之座,执行命令后需重新登录以生效。" + "description": "重置当前角色的命之座,执行命令后需重新登录以生效" }, "resetShopLimit": { "usage": "用法:/resetshop <玩家ID>", - "description": "重置所选玩家的商店刷新时间。" + "description": "重置所选玩家的商店刷新时间" }, "sendMail": { "usage": "用法:give [玩家] <物品ID|物品名称> [数量]", @@ -246,19 +246,19 @@ "sender": "<发件人>", "arguments": "<物品ID|物品名称|finish> [数量] [等级]", "error": "错误:无效的编写阶段 %s。需要 StackTrace 请查看服务器控制台。", - "description": "向指定用户发送邮件。此命令的用法可根据附加的参数而变化。" + "description": "向指定用户发送邮件。此命令的用法可根据附加的参数而变化" }, "sendMessage": { "usage": "用法:sendmessage <玩家> <消息>", "success": "消息已发送。", - "description": "向指定玩家发送消息。" + "description": "向指定玩家发送消息" }, "setFetterLevel": { "usage": "用法:setfetterlevel <好感度等级>", "range_error": "好感度等级必须在 0 到 10 之间。", - "success": "好感度已设置为 %s 级", + "success": "好感度已设置为 %s 级。", "level_error": "无效的好感度等级。", - "description": "设置当前角色的好感度等级。" + "description": "设置当前角色的好感度等级" }, "setStats": { "usage_console": "用法:setstats|stats @ <属性> <数值>", @@ -270,23 +270,23 @@ "set_self": "%s 已设为 %s。", "set_for_uid": "将 %s (来自 %s) 设置为 %s。", "set_max_hp": "最大生命值已设为 %s。", - "description": "设置当前角色的属性。" + "description": "设置当前角色的属性" }, "setWorldLevel": { "usage": "用法:setworldlevel <等级>", "value_error": "世界等级必须设置在0-8之间。", "success": "已将世界等级设为 %s。", "invalid_world_level": "无效的世界等级。", - "description": "设置世界等级,执行命令后需重新登录以生效。" + "description": "设置世界等级,执行命令后需重新登录以生效" }, "spawn": { "usage": "用法:spawn <实体ID> [数量] [等级(仅怪物)]", "success": "已生成 %s 个 %s。", - "description": "在你附近生成一个生物。" + "description": "在你附近生成一个生物" }, "stop": { "success": "正在关闭服务器...", - "description": "停止服务器。" + "description": "停止服务器" }, "talent": { "usage_1": "设置天赋等级:/talent set <天赋ID> <数值>", @@ -303,20 +303,20 @@ "normal_attack_id": "普通攻击的 ID 为 %s。", "e_skill_id": "元素战技ID %s。", "q_skill_id": "元素爆发ID %s。", - "description": "设置当前角色的天赋等级。" + "description": "设置当前角色的天赋等级" }, "teleportAll": { - "success": "已将所有玩家传送到你的位置", + "success": "已将所有玩家传送到你的位置。", "error": "你只能在多人游戏状态下执行此命令。", - "description": "将你世界中的所有玩家传送到你所在的位置。" + "description": "将你世界中的所有玩家传送到你所在的位置" }, "teleport": { "usage_server": "用法:/tp @<玩家ID> [场景ID]", "usage": "用法:/tp [@<玩家ID>] [场景ID]", "specify_player_id": "你必须指定一个玩家ID。", "invalid_position": "无效的位置。", - "success": "传送 %s 到坐标 %s,%s,%s,场景为 %s", - "description": "改变指定玩家的位置。" + "success": "传送 %s 到坐标 %s,%s,%s,场景为 %s。", + "description": "改变指定玩家的位置" }, "tower": { "unlock_done": "深境回廊的所有层已全部解锁。" @@ -325,28 +325,28 @@ "usage": "用法:weather <天气ID> [气候ID]", "success": "已更改天气为 %s,气候为 %s。", "invalid_id": "无效的天气ID。", - "description": "更改天气。" + "description": "更改天气" }, "drop": { "command_usage": "用法:drop <物品ID|物品名称> [数量]", "success": "已丢下 %s 个 %s。", - "description": "在你附近丢下一个物品。" + "description": "在你附近丢下一个物品" }, "help": { "usage": "用法:", "aliases": "别名:", "available_commands": "可用命令:", - "description": "发送帮助信息或显示指定命令的信息。" + "description": "发送帮助信息或显示指定命令的信息" }, "restart": { - "description": "重新启动服务器。" + "description": "重新启动服务器" }, "unlocktower": { "success": "解锁完成。", - "description": "解锁深境螺旋的所有层。" + "description": "解锁深境螺旋的所有层" }, "resetshop": { - "description": "重置商店刷新时间。" + "description": "重置商店刷新时间" } } } From 9fc18151c94e4d6c0aa96d4169db7eb12586252f Mon Sep 17 00:00:00 2001 From: tester233 <105267106+tester233@users.noreply.github.com> Date: Wed, 11 May 2022 19:37:52 +0800 Subject: [PATCH 06/59] Improve text --- src/main/resources/languages/zh-CN.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 01572a20b..111e3c0c5 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -20,7 +20,7 @@ "unhandled_request_error": "[Dispatch] 潜在的未处理请求:%s %s", "account": { "login_attempt": "[Dispatch] 客户端 %s 正在尝试登录", - "login_success": "[Dispatch] 客户端 %s 已登录,UID为 %s", + "login_success": "[Dispatch] 客户端 %s 已登录,UID 为 %s", "login_token_attempt": "[Dispatch] 客户端 %s 正在尝试使用 token 登录", "login_token_error": "[Dispatch] 客户端 %s 使用 token 登录失败", "login_token_success": "[Dispatch] 客户端 %s 已通过 token 登录,UID 为 %s", @@ -96,7 +96,7 @@ "create": "已创建账号,UID 为 %s。", "delete": "账号已删除。", "no_account": "账号不存在。", - "command_usage": "用法:account <用户名> [uid]", + "command_usage": "用法:account <用户名> [UID]", "description": "创建或删除账号" }, "broadcast": { @@ -290,7 +290,7 @@ }, "talent": { "usage_1": "设置天赋等级:/talent set <天赋ID> <数值>", - "usage_2": "另一种设置天赋等级的方法:/talent <数值>", + "usage_2": "另一种设置天赋等级的方法:/talent <数值>", "usage_3": "获取天赋ID:/talent getid", "lower_16": "无效的天赋等级,天赋等级应小于等于15。", "set_id": "将天赋等级设为 %s。", From a77ae0bc3c1001997eb29d0bf23c5e83113a5b70 Mon Sep 17 00:00:00 2001 From: mingjun97 Date: Tue, 10 May 2022 18:34:30 -0700 Subject: [PATCH 07/59] Introduce `-version` argument to display version --- build.gradle | 16 ++++++++++++++++ src/main/java/emu/grasscutter/Grasscutter.java | 4 ++++ 2 files changed, 20 insertions(+) diff --git a/build.gradle b/build.gradle index 4434ed28e..186a6d440 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,14 @@ targetCompatibility = JavaVersion.VERSION_17 group = 'xyz.grasscutters' version = '1.1.1-dev' +def gitCommitHash = { + try { + return 'git rev-parse --verify --short HEAD'.execute().text.trim() + } catch (e) { + return "GIT_NOT_FOUND" + } +} + sourceCompatibility = 17 targetCompatibility = 17 @@ -97,6 +105,7 @@ application { mainClassName = 'emu.grasscutter.Grasscutter' } + jar { manifest { attributes 'Main-Class': 'emu.grasscutter.Grasscutter' @@ -113,6 +122,13 @@ jar { from('src/main/java') { include '*.xml' } + new File(projectDir, "src/generated/main/java/emu/grasscutter/BuildConfig.java").text = """ + package emu.grasscutter; + public class BuildConfig { + public static final String VERSION = \"${version}\"; + public static final String GIT_HASH = \"${gitCommitHash()}\"; + } + """ destinationDir = file(".") } diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 73e761e6e..a192815d1 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -29,6 +29,7 @@ import emu.grasscutter.server.dispatch.DispatchServer; import emu.grasscutter.server.game.GameServer; import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.Crypto; +import emu.grasscutter.BuildConfig; import javax.annotation.Nullable; @@ -82,6 +83,9 @@ public final class Grasscutter { case "-gachamap" -> { Tools.createGachaMapping(DATA("gacha_mappings.js")); exitEarly = true; } + case "-version" -> { + System.out.println("Grasscutter version: " + BuildConfig.VERSION + "\nGit Hash: " + BuildConfig.GIT_HASH); exitEarly = true; + } } } From 170db70b6fc503488b911a9acdbceeae73426815 Mon Sep 17 00:00:00 2001 From: mingjun97 Date: Tue, 10 May 2022 21:11:19 -0700 Subject: [PATCH 08/59] Fix github action build issue * Move `BuildConfig.java` from `/src/generated`to `/src/main` to accomplish the building pipeline * Add BuildConfig.java to the .gitignore --- .gitignore | 1 + build.gradle | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9fa6c9427..6fd78ed3b 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,7 @@ language/ languages/ gacha-mapping.js data/gacha_mappings.js +BuildConfig.java # macOS .DS_Store diff --git a/build.gradle b/build.gradle index 186a6d440..47f433b57 100644 --- a/build.gradle +++ b/build.gradle @@ -122,7 +122,7 @@ jar { from('src/main/java') { include '*.xml' } - new File(projectDir, "src/generated/main/java/emu/grasscutter/BuildConfig.java").text = """ + new File(projectDir, "src/main/java/emu/grasscutter/BuildConfig.java").text = """ package emu.grasscutter; public class BuildConfig { public static final String VERSION = \"${version}\"; From 895e2bc44ae321b19ccf3c397cce49eb44fc523c Mon Sep 17 00:00:00 2001 From: mingjun97 Date: Tue, 10 May 2022 21:36:30 -0700 Subject: [PATCH 09/59] Display version info at console starting --- src/main/java/emu/grasscutter/Grasscutter.java | 1 + src/main/resources/languages/en-US.json | 3 ++- src/main/resources/languages/pl-PL.json | 3 ++- src/main/resources/languages/zh-CN.json | 3 ++- src/main/resources/languages/zh-TW.json | 3 ++- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index a192815d1..b328a453b 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -190,6 +190,7 @@ public final class Grasscutter { } getLogger().info(translate("messages.status.done")); + getLogger().info(translate("messages.status.version", BuildConfig.VERSION, BuildConfig.GIT_HASH)); String input = null; boolean isLastInterrupted = false; while (true) { diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 2b392b682..099e36eaa 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -48,7 +48,8 @@ "run_mode_error": "Invalid server run mode: %s.", "run_mode_help": "Server run mode must be 'HYBRID', 'DISPATCH_ONLY', or 'GAME_ONLY'. Unable to start Grasscutter...", "create_resources": "Creating resources folder...", - "resources_error": "Place a copy of 'BinOutput' and 'ExcelBinOutput' in the resources folder." + "resources_error": "Place a copy of 'BinOutput' and 'ExcelBinOutput' in the resources folder.", + "version": "Grasscutter version: %s, Git Hash: %s" } }, "commands": { diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json index 8f76d8951..0f5d88aa9 100644 --- a/src/main/resources/languages/pl-PL.json +++ b/src/main/resources/languages/pl-PL.json @@ -45,7 +45,8 @@ "run_mode_error": "Błędny tryb pracy serwera: %s.", "run_mode_help": "Tryb pracy serwera musi być ustawiony na 'HYBRID', 'DISPATCH_ONLY', lub 'GAME_ONLY'. Nie można wystartować Grasscutter...", "create_resources": "Tworzenie folderu resources...", - "resources_error": "Umieść kopię 'BinOutput' i 'ExcelBinOutput' w folderze resources." + "resources_error": "Umieść kopię 'BinOutput' i 'ExcelBinOutput' w folderze resources.", + "version": "Grasscutter versión: %s, Git Hash: %s" } }, "commands": { diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 111e3c0c5..5f2b02736 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -45,7 +45,8 @@ "run_mode_error": "无效的服务器运行模式:%s。", "run_mode_help": "服务器运行模式必须为 HYBRID、DISPATCH_ONLY 或 GAME_ONLY。Grasscutter 启动失败...", "create_resources": "正在创建 resources 目录...", - "resources_error": "请将 BinOutput 和 ExcelBinOutput 复制到 resources 目录。" + "resources_error": "请将 BinOutput 和 ExcelBinOutput 复制到 resources 目录。", + "version": "Grasscutter版本: %s, Git Hash: %s" } }, "commands": { diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index 9c3c99686..3cb850415 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -45,7 +45,8 @@ "run_mode_error": "無效的伺服器運行模式: %s。", "run_mode_help": "伺服器運行模式必須為 HYBRID 或者 DISPATCH_ONLY 或者 GAME_ONLY。Grasscutter 啟動失敗...", "create_resources": "正在建立 resources 資料夾...", - "resources_error": "請將 BinOutput 和 ExcelBinOutput 複製到 resources 資料夾。" + "resources_error": "請將 BinOutput 和 ExcelBinOutput 複製到 resources 資料夾。", + "version": "Grasscutter版本: %s, Git Hash: %s" } }, "commands": { From 570635ea02a4824b68d8e98de3ed4c26cb7ae8e7 Mon Sep 17 00:00:00 2001 From: mingjun97 Date: Tue, 10 May 2022 23:23:58 -0700 Subject: [PATCH 10/59] Revise version format --- src/main/java/emu/grasscutter/Grasscutter.java | 2 +- src/main/resources/languages/en-US.json | 2 +- src/main/resources/languages/pl-PL.json | 2 +- src/main/resources/languages/zh-CN.json | 2 +- src/main/resources/languages/zh-TW.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index b328a453b..6ca78dfa9 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -84,7 +84,7 @@ public final class Grasscutter { Tools.createGachaMapping(DATA("gacha_mappings.js")); exitEarly = true; } case "-version" -> { - System.out.println("Grasscutter version: " + BuildConfig.VERSION + "\nGit Hash: " + BuildConfig.GIT_HASH); exitEarly = true; + System.out.println("Grasscutter version: " + BuildConfig.VERSION + "-" + BuildConfig.GIT_HASH); exitEarly = true; } } } diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 099e36eaa..81e97eed7 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -49,7 +49,7 @@ "run_mode_help": "Server run mode must be 'HYBRID', 'DISPATCH_ONLY', or 'GAME_ONLY'. Unable to start Grasscutter...", "create_resources": "Creating resources folder...", "resources_error": "Place a copy of 'BinOutput' and 'ExcelBinOutput' in the resources folder.", - "version": "Grasscutter version: %s, Git Hash: %s" + "version": "Grasscutter version: %s-%s" } }, "commands": { diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json index 0f5d88aa9..aa06723d8 100644 --- a/src/main/resources/languages/pl-PL.json +++ b/src/main/resources/languages/pl-PL.json @@ -46,7 +46,7 @@ "run_mode_help": "Tryb pracy serwera musi być ustawiony na 'HYBRID', 'DISPATCH_ONLY', lub 'GAME_ONLY'. Nie można wystartować Grasscutter...", "create_resources": "Tworzenie folderu resources...", "resources_error": "Umieść kopię 'BinOutput' i 'ExcelBinOutput' w folderze resources.", - "version": "Grasscutter versión: %s, Git Hash: %s" + "version": "Grasscutter versión: %s-%s" } }, "commands": { diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 5f2b02736..84d4a8c94 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -46,7 +46,7 @@ "run_mode_help": "服务器运行模式必须为 HYBRID、DISPATCH_ONLY 或 GAME_ONLY。Grasscutter 启动失败...", "create_resources": "正在创建 resources 目录...", "resources_error": "请将 BinOutput 和 ExcelBinOutput 复制到 resources 目录。", - "version": "Grasscutter版本: %s, Git Hash: %s" + "version": "Grasscutter版本: %s-%s" } }, "commands": { diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index 3cb850415..8e7a75949 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -46,7 +46,7 @@ "run_mode_help": "伺服器運行模式必須為 HYBRID 或者 DISPATCH_ONLY 或者 GAME_ONLY。Grasscutter 啟動失敗...", "create_resources": "正在建立 resources 資料夾...", "resources_error": "請將 BinOutput 和 ExcelBinOutput 複製到 resources 資料夾。", - "version": "Grasscutter版本: %s, Git Hash: %s" + "version": "Grasscutter版本: %s-%s" } }, "commands": { From 41de6bd229c0329da9a12ab8570266b4b879bd4a Mon Sep 17 00:00:00 2001 From: mingjun97 Date: Tue, 10 May 2022 23:34:53 -0700 Subject: [PATCH 11/59] Make `injectGitHash` as a task --- build.gradle | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index 47f433b57..ffb6a498e 100644 --- a/build.gradle +++ b/build.gradle @@ -45,13 +45,6 @@ targetCompatibility = JavaVersion.VERSION_17 group = 'xyz.grasscutters' version = '1.1.1-dev' -def gitCommitHash = { - try { - return 'git rev-parse --verify --short HEAD'.execute().text.trim() - } catch (e) { - return "GIT_NOT_FOUND" - } -} sourceCompatibility = 17 targetCompatibility = 17 @@ -122,13 +115,6 @@ jar { from('src/main/java') { include '*.xml' } - new File(projectDir, "src/main/java/emu/grasscutter/BuildConfig.java").text = """ - package emu.grasscutter; - public class BuildConfig { - public static final String VERSION = \"${version}\"; - public static final String GIT_HASH = \"${gitCommitHash()}\"; - } - """ destinationDir = file(".") } @@ -242,6 +228,26 @@ javadoc { } } +task injectGitHash { + doLast { + def gitCommitHash = { + try { + return 'git rev-parse --verify --short HEAD'.execute().text.trim() + } catch (e) { + return "GIT_NOT_FOUND" + } + } + new File(projectDir, "src/main/java/emu/grasscutter/BuildConfig.java").text = """ + package emu.grasscutter; + public class BuildConfig { + public static final String VERSION = \"${version}\"; + public static final String GIT_HASH = \"${gitCommitHash()}\"; + } + """ + } +} + processResources { dependsOn "generateProto" + dependsOn "injectGitHash" } From c105c71e533cbdf30bc6642c036d9f590ab6cb49 Mon Sep 17 00:00:00 2001 From: mingjun97 Date: Tue, 10 May 2022 23:55:40 -0700 Subject: [PATCH 12/59] Fix building error --- build.gradle | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index ffb6a498e..3a8d2c34d 100644 --- a/build.gradle +++ b/build.gradle @@ -229,25 +229,22 @@ javadoc { } task injectGitHash { - doLast { - def gitCommitHash = { - try { - return 'git rev-parse --verify --short HEAD'.execute().text.trim() - } catch (e) { - return "GIT_NOT_FOUND" - } + def gitCommitHash = { + try { + return 'git rev-parse --verify --short HEAD'.execute().text.trim() + } catch (e) { + return "GIT_NOT_FOUND" } - new File(projectDir, "src/main/java/emu/grasscutter/BuildConfig.java").text = """ - package emu.grasscutter; - public class BuildConfig { - public static final String VERSION = \"${version}\"; - public static final String GIT_HASH = \"${gitCommitHash()}\"; - } - """ } + new File(projectDir, "src/main/java/emu/grasscutter/BuildConfig.java").text = """ + package emu.grasscutter; + public class BuildConfig { + public static final String VERSION = \"${version}\"; + public static final String GIT_HASH = \"${gitCommitHash()}\"; + } + """ } processResources { dependsOn "generateProto" - dependsOn "injectGitHash" } From 57c7f7a43b3ed20194c1abccdfa662dd0efa3dcd Mon Sep 17 00:00:00 2001 From: ImmuState Date: Wed, 11 May 2022 11:19:25 -0700 Subject: [PATCH 13/59] Add gacha details page. --- data/gacha_details.html | 121 ++++++++++++++++++ .../grasscutter/game/gacha/GachaBanner.java | 9 +- .../grasscutter/game/gacha/GachaManager.java | 18 ++- .../server/dispatch/DispatchServer.java | 4 + .../dispatch/http/GachaDetailsHandler.java | 90 +++++++++++++ src/main/resources/languages/en-US.json | 9 ++ src/main/resources/languages/pl-PL.json | 9 ++ src/main/resources/languages/zh-CN.json | 9 ++ src/main/resources/languages/zh-TW.json | 9 ++ 9 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 data/gacha_details.html create mode 100644 src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java diff --git a/data/gacha_details.html b/data/gacha_details.html new file mode 100644 index 000000000..16cf7313a --- /dev/null +++ b/data/gacha_details.html @@ -0,0 +1,121 @@ + + + + + + + + + Banner Details + + + +
+
+

{{TITLE}}

+ +

{{AVAILABLE_FIVE_STARS}}

+
+
    +
+ +

{{AVAILABLE_FOUR_STARS}}

+
+
    +
+ +

{{AVAILABLE_THREE_STARS}}

+
+
    +
+
+
+
+ +
+ + + + diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index dce433fcf..7a2646a4f 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -102,6 +102,11 @@ public class GachaBanner { + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + "/gacha?s=" + sessionKey + "&gachaType=" + gachaType; + String details = "http" + (DISPATCH_INFO.encryption.useInRouting ? "s" : "") + "://" + + lr(DISPATCH_INFO.accessAddress, DISPATCH_INFO.bindAddress) + ":" + + lr(DISPATCH_INFO.accessPort, DISPATCH_INFO.bindPort) + + "/gacha/details?s=" + sessionKey + "&gachaType=" + gachaType; + // Grasscutter.getLogger().info("record = " + record); GachaInfo.Builder info = GachaInfo.newBuilder() .setGachaType(this.getGachaType()) @@ -112,8 +117,8 @@ public class GachaBanner { .setCostItemNum(1) .setGachaPrefabPath(this.getPrefabPath()) .setGachaPreviewPrefabPath(this.getPreviewPrefabPath()) - .setGachaProbUrl(record) - .setGachaProbUrlOversea(record) + .setGachaProbUrl(details) + .setGachaProbUrlOversea(details) .setGachaRecordUrl(record) .setGachaRecordUrlOversea(record) .setTenCostItemId(this.getCostItem()) diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index 03edca09a..f0baf65a9 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -65,7 +65,23 @@ public class GachaManager { public Int2ObjectMap getGachaBanners() { return gachaBanners; } - + + public int[] getYellowAvatars() { + return this.yellowAvatars; + } + public int[] getYellowWeapons() { + return this.yellowWeapons; + } + public int[] getPurpleAvatars() { + return this.purpleAvatars; + } + public int[] getPurpleWeapons() { + return this.purpleWeapons; + } + public int[] getBlueWeapons() { + return this.blueWeapons; + } + public int randomRange(int min, int max) { return ThreadLocalRandom.current().nextInt(max - min + 1) + min; } diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index 4e09f8881..c78aff7c6 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -16,6 +16,7 @@ import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo; import emu.grasscutter.server.dispatch.authentication.AuthenticationHandler; import emu.grasscutter.server.dispatch.authentication.DefaultAuthenticationHandler; +import emu.grasscutter.server.dispatch.http.GachaDetailsHandler; import emu.grasscutter.server.dispatch.http.GachaRecordHandler; import emu.grasscutter.server.dispatch.json.*; import emu.grasscutter.server.dispatch.json.ComboTokenReqJson.LoginTokenData; @@ -455,6 +456,9 @@ public final class DispatchServer { httpServer.raw().config.addSinglePageRoot("/gacha/mappings", gachaMappingsPath, Location.EXTERNAL); + // gacha details + httpServer.get("/gacha/details", new GachaDetailsHandler()); + // static file support for plugins httpServer.raw().config.precompressStaticFiles = false; // If this isn't set to false, files such as images may appear corrupted when serving static files diff --git a/src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java b/src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java new file mode 100644 index 000000000..d46cead40 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java @@ -0,0 +1,90 @@ +package emu.grasscutter.server.dispatch.http; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.Account; +import emu.grasscutter.game.gacha.GachaBanner; +import emu.grasscutter.game.gacha.GachaManager; +import emu.grasscutter.game.gacha.GachaBanner.BannerType; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.Utils; +import express.http.HttpContextHandler; +import express.http.Request; +import express.http.Response; + +import static emu.grasscutter.utils.Language.translate; + +import static emu.grasscutter.Configuration.*; + +public final class GachaDetailsHandler implements HttpContextHandler { + private final String render_template; + + public GachaDetailsHandler() { + File template = new File(Utils.toFilePath(DATA("/gacha_details.html"))); + this.render_template = template.exists() ? new String(FileUtils.read(template)) : null; + } + + @Override + public void handle(Request req, Response res) throws IOException { + String response = this.render_template; + + // Get player info (for langauge). + String sessionKey = req.query("s"); + Account account = DatabaseHelper.getAccountBySessionKey(sessionKey); + Player player = Grasscutter.getGameServer().getPlayerByUid(account.getPlayerUid()); + + // If the template was not loaded, return an error. + if (this.render_template == null) { + res.send(translate(player, "gacha.details.template_missing")); + return; + } + + // Add translated title etc. to the page. + response = response.replace("{{TITLE}}", translate(player, "gacha.details.title")); + response = response.replace("{{AVAILABLE_FIVE_STARS}}", translate(player, "gacha.details.available_five_stars")); + response = response.replace("{{AVAILABLE_FOUR_STARS}}", translate(player, "gacha.details.available_four_stars")); + response = response.replace("{{AVAILABLE_THREE_STARS}}", translate(player, "gacha.details.available_three_stars")); + + // Get the banner info for the banner we want. + int gachaType = Integer.parseInt(req.query("gachaType")); + GachaManager manager = Grasscutter.getGameServer().getGachaManager(); + GachaBanner banner = manager.getGachaBanners().get(gachaType); + + // Add 5-star items. + Set fiveStarItems = new LinkedHashSet<>(); + + Arrays.stream(banner.getRateUpItems1()).forEach(i -> fiveStarItems.add(Integer.toString(i))); + if (banner.getBannerType() == BannerType.STANDARD || banner.getBannerType() == BannerType.EVENT) { + Arrays.stream(manager.getYellowAvatars()).forEach(i -> fiveStarItems.add(Integer.toString(i))); + } + if (banner.getBannerType() == BannerType.STANDARD || banner.getBannerType() == BannerType.WEAPON) { + Arrays.stream(manager.getYellowWeapons()).forEach(i -> fiveStarItems.add(Integer.toString(i))); + } + + response = response.replace("{{FIVE_STARS}}", "[" + String.join(",", fiveStarItems) + "]"); + + // Add 4-star items. + Set fourStarItems = new LinkedHashSet<>(); + + Arrays.stream(banner.getRateUpItems2()).forEach(i -> fourStarItems.add(Integer.toString(i))); + Arrays.stream(manager.getPurpleAvatars()).forEach(i -> fourStarItems.add(Integer.toString(i))); + Arrays.stream(manager.getPurpleWeapons()).forEach(i -> fourStarItems.add(Integer.toString(i))); + + response = response.replace("{{FOUR_STARS}}", "[" + String.join(",", fourStarItems) + "]"); + + // Add 3-star items. + Set threeStarItems = new LinkedHashSet<>(); + Arrays.stream(manager.getBlueWeapons()).forEach(i -> threeStarItems.add(Integer.toString(i))); + response = response.replace("{{THREE_STARS}}", "[" + String.join(",", threeStarItems) + "]"); + + // Done. + res.send(response); + } +} diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 81e97eed7..42b52d7f5 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -352,5 +352,14 @@ "resetshop": { "description": "reset shop" } + }, + "gacha": { + "details": { + "title": "Banner Details", + "available_five_stars": "Available 5-star Items", + "available_four_stars": "Available 4-star Items", + "available_three_stars": "Available 3-star Items", + "template_missing": "data/gacha_details.html is missing." + } } } diff --git a/src/main/resources/languages/pl-PL.json b/src/main/resources/languages/pl-PL.json index aa06723d8..e5eff2d84 100644 --- a/src/main/resources/languages/pl-PL.json +++ b/src/main/resources/languages/pl-PL.json @@ -302,5 +302,14 @@ "resetshop": { "description": "zresetuj sklep" } + }, + "gacha": { + "details": { + "title": "Banner Details", + "available_five_stars": "Available 5-star Items", + "available_four_stars": "Available 4-star Items", + "available_three_stars": "Available 3-star Items", + "template_missing": "data/gacha_details.html is missing." + } } } \ No newline at end of file diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 84d4a8c94..ac46370d5 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -349,5 +349,14 @@ "resetshop": { "description": "重置商店刷新时间" } + }, + "gacha": { + "details": { + "title": "Banner Details", + "available_five_stars": "Available 5-star Items", + "available_four_stars": "Available 4-star Items", + "available_three_stars": "Available 3-star Items", + "template_missing": "data/gacha_details.html is missing." + } } } diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index 8e7a75949..f6ae52c9d 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -302,5 +302,14 @@ "resetshop": { "description": "重置商店時間" } + }, + "gacha": { + "details": { + "title": "Banner Details", + "available_five_stars": "Available 5-star Items", + "available_four_stars": "Available 4-star Items", + "available_three_stars": "Available 3-star Items", + "template_missing": "data/gacha_details.html is missing." + } } } From e5a85f81c27d564d3ddefb960e44a8c8c7a1478a Mon Sep 17 00:00:00 2001 From: ImmuState Date: Wed, 11 May 2022 11:36:14 -0700 Subject: [PATCH 14/59] Insert language setting based on the player's account. --- data/gacha_details.html | 10 +++++----- .../server/dispatch/http/GachaDetailsHandler.java | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/data/gacha_details.html b/data/gacha_details.html index 16cf7313a..ccd775ef6 100644 --- a/data/gacha_details.html +++ b/data/gacha_details.html @@ -38,7 +38,7 @@

{{TITLE}}

- +

{{AVAILABLE_FIVE_STARS}}


    @@ -81,11 +81,11 @@ @@ -161,32 +128,12 @@ } return "" + itemID + ""; } - function dateFormatter(timeStamp) { - var date = new Date(timeStamp); - if (lang == "en-us" || lang == null) { // MM/DD/YYYY hh:mm:ss.SSS - return String(date.getMonth()+1).padStart(2, "0") + - "/"+String(date.getDate()).padStart(2, "0")+ - "/"+date.getFullYear()+ - " "+String(date.getHours()).padStart(2, "0")+ - ":"+String(date.getMinutes()).padStart(2, "0")+ - ":"+String(date.getSeconds()).padStart(2, "0")+ - "."+String(date.getMilliseconds()).padStart(3, "0"); - } else if (lang == "zh-cn") { // YYYY/MM/DD hh:mm:ss.SSS - return date.getFullYear()+ - "/" + String(date.getMonth()+1).padStart(2, "0") + - "/"+String(date.getDate()).padStart(2, "0")+ - " "+String(date.getHours()).padStart(2, "0")+ - ":"+String(date.getMinutes()).padStart(2, "0")+ - ":"+String(date.getSeconds()).padStart(2, "0")+ - "."+String(date.getMilliseconds()).padStart(3, "0"); - } - } (function (){ var container = document.getElementById("container"); record.forEach(element => { var e = document.createElement("tr"); - e.innerHTML= "" + dateFormatter(element.time) + "" + itemMapper(element.item) + ""; + e.innerHTML= "" + (new Date(element.time).toLocaleString(lang)) + "" + itemMapper(element.item) + ""; container.appendChild(e); }); // setup pagenation buttons From 3c55aa64eb3880aace707bc58fff083870fac4d0 Mon Sep 17 00:00:00 2001 From: Akka <104902222+Akka0@users.noreply.github.com> Date: Fri, 13 May 2022 14:59:05 +0800 Subject: [PATCH 35/59] fix: LEAK: ByteBuf.release() was not called --- src/main/java/emu/grasscutter/server/game/GameSession.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/emu/grasscutter/server/game/GameSession.java b/src/main/java/emu/grasscutter/server/game/GameSession.java index 7cc9a799f..cf6386770 100644 --- a/src/main/java/emu/grasscutter/server/game/GameSession.java +++ b/src/main/java/emu/grasscutter/server/game/GameSession.java @@ -252,6 +252,7 @@ public class GameSession extends KcpChannel { } catch (Exception e) { e.printStackTrace(); } finally { + data.release(); packet.release(); } } From 631a53030c5d86242aa6214aa3a01b4c2a19eb4e Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 13 May 2022 03:12:25 -0700 Subject: [PATCH 36/59] Switch to using quest excels --- .../java/emu/grasscutter/data/GameData.java | 13 +- .../emu/grasscutter/data/ResourceLoader.java | 17 +-- .../data/custom/MainQuestData.java | 53 ++++++++ .../grasscutter/data/custom/QuestConfig.java | 25 ---- .../data/custom/QuestConfigData.java | 104 ---------------- .../emu/grasscutter/data/def/QuestData.java | 115 ++++++++++++++++++ .../grasscutter/game/quest/GameMainQuest.java | 1 - .../emu/grasscutter/game/quest/GameQuest.java | 49 +++++--- .../grasscutter/game/quest/QuestManager.java | 27 ++-- .../grasscutter/game/quest/QuestValue.java | 4 +- .../game/quest/ServerQuestHandler.java | 12 +- .../game/quest/conditions/BaseCondition.java | 6 +- .../ConditionPlayerLevelEqualGreater.java | 8 +- .../quest/conditions/ConditionStateEqual.java | 10 +- .../game/quest/content/BaseContent.java | 6 +- .../quest/content/ContentCompleteTalk.java | 6 +- ...uestTriggerType.java => QuestTrigger.java} | 4 +- .../game/quest/handlers/QuestBaseHandler.java | 2 +- .../server/packet/recv/HandlerNpcTalkReq.java | 4 +- ...etServerCondMeetQuestListUpdateNotify.java | 3 - .../java/emu/grasscutter/tools/Tools.java | 12 +- 21 files changed, 262 insertions(+), 219 deletions(-) create mode 100644 src/main/java/emu/grasscutter/data/custom/MainQuestData.java delete mode 100644 src/main/java/emu/grasscutter/data/custom/QuestConfig.java delete mode 100644 src/main/java/emu/grasscutter/data/custom/QuestConfigData.java create mode 100644 src/main/java/emu/grasscutter/data/def/QuestData.java rename src/main/java/emu/grasscutter/game/quest/enums/{QuestTriggerType.java => QuestTrigger.java} (99%) diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 2b40818e1..75b840202 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -12,7 +12,7 @@ import emu.grasscutter.data.custom.AbilityEmbryoEntry; import emu.grasscutter.data.custom.AbilityModifier; import emu.grasscutter.data.custom.AbilityModifierEntry; import emu.grasscutter.data.custom.OpenConfigEntry; -import emu.grasscutter.data.custom.QuestConfig; +import emu.grasscutter.data.custom.MainQuestData; import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.data.def.*; import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; @@ -28,7 +28,7 @@ public class GameData { private static final Map abilityModifiers = new HashMap<>(); private static final Map openConfigEntries = new HashMap<>(); private static final Map scenePointEntries = new HashMap<>(); - private static final Int2ObjectMap questConfigs = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap mainQuestData = new Int2ObjectOpenHashMap<>(); // ExcelConfigs private static final Int2ObjectMap playerLevelDataMap = new Int2ObjectOpenHashMap<>(); @@ -70,6 +70,7 @@ public class GameData { private static final Int2ObjectMap worldLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap dailyDungeonDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap dungeonDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap questDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap shopGoodsDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap combineDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap rewardPreviewDataMap = new Int2ObjectOpenHashMap<>(); @@ -124,8 +125,8 @@ public class GameData { return getScenePointEntries().get(sceneId + "_" + pointId); } - public static Int2ObjectMap getQuestConfigs() { - return questConfigs; + public static Int2ObjectMap getMainQuestDataMap() { + return mainQuestData; } public static Int2ObjectMap getAvatarDataMap() { @@ -337,4 +338,8 @@ public class GameData { public static Int2ObjectMap getTowerScheduleDataMap(){ return towerScheduleDataMap; } + + public static Int2ObjectMap getQuestDataMap() { + return questDataMap; + } } diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index 5c2ac1ee6..4b940c44d 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -24,9 +24,7 @@ import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction; import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType; import emu.grasscutter.data.custom.AbilityModifierEntry; import emu.grasscutter.data.custom.OpenConfigEntry; -import emu.grasscutter.data.custom.QuestConfig; -import emu.grasscutter.data.custom.QuestConfigData; -import emu.grasscutter.data.custom.QuestConfigData.SubQuestConfigData; +import emu.grasscutter.data.custom.MainQuestData; import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.game.world.SpawnDataEntry.*; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -407,24 +405,19 @@ public class ResourceLoader { } for (File file : folder.listFiles()) { - QuestConfigData mainQuest = null; + MainQuestData mainQuest = null; try (FileReader fileReader = new FileReader(file)) { - mainQuest = Grasscutter.getGsonFactory().fromJson(fileReader, QuestConfigData.class); + mainQuest = Grasscutter.getGsonFactory().fromJson(fileReader, MainQuestData.class); } catch (Exception e) { e.printStackTrace(); continue; } - if (mainQuest.getSubQuests() != null) { - for (SubQuestConfigData subQuest : mainQuest.getSubQuests()) { - QuestConfig quest = new QuestConfig(mainQuest, subQuest); - GameData.getQuestConfigs().put(quest.getId(), quest); - } - } + GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest); } - Grasscutter.getLogger().info("Loaded " + GameData.getQuestConfigs().size() + " Quest Configs"); + Grasscutter.getLogger().info("Loaded " + GameData.getMainQuestDataMap().size() + " MainQuestDatas."); } // BinOutput configs diff --git a/src/main/java/emu/grasscutter/data/custom/MainQuestData.java b/src/main/java/emu/grasscutter/data/custom/MainQuestData.java new file mode 100644 index 000000000..e405e3598 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/custom/MainQuestData.java @@ -0,0 +1,53 @@ +package emu.grasscutter.data.custom; + +import emu.grasscutter.game.quest.enums.LogicType; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.enums.QuestType; + +public class MainQuestData { + private int id; + private int series; + private QuestType type; + + private long titleTextMapHash; + private int[] suggestTrackMainQuestList; + private int[] rewardIdList; + + private SubQuestData[] subQuests; + + public int getId() { + return id; + } + + public int getSeries() { + return series; + } + + public QuestType getType() { + return type; + } + + public long getTitleTextMapHash() { + return titleTextMapHash; + } + + public int[] getSuggestTrackMainQuestList() { + return suggestTrackMainQuestList; + } + + public int[] getRewardIdList() { + return rewardIdList; + } + + public SubQuestData[] getSubQuests() { + return subQuests; + } + + public static class SubQuestData { + private int subId; + + public int getSubId() { + return subId; + } + } +} diff --git a/src/main/java/emu/grasscutter/data/custom/QuestConfig.java b/src/main/java/emu/grasscutter/data/custom/QuestConfig.java deleted file mode 100644 index 8674ff7ab..000000000 --- a/src/main/java/emu/grasscutter/data/custom/QuestConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package emu.grasscutter.data.custom; - -import emu.grasscutter.data.custom.QuestConfigData.SubQuestConfigData; - -public class QuestConfig { - private final QuestConfigData mainQuest; - private final SubQuestConfigData subQuest; - - public QuestConfig(QuestConfigData mainQuest, SubQuestConfigData subQuest) { - this.mainQuest = mainQuest; - this.subQuest = subQuest; - } - - public int getId() { - return subQuest.getSubId(); - } - - public QuestConfigData getMainQuest() { - return mainQuest; - } - - public SubQuestConfigData getSubQuest() { - return subQuest; - } -} diff --git a/src/main/java/emu/grasscutter/data/custom/QuestConfigData.java b/src/main/java/emu/grasscutter/data/custom/QuestConfigData.java deleted file mode 100644 index 3ede024f2..000000000 --- a/src/main/java/emu/grasscutter/data/custom/QuestConfigData.java +++ /dev/null @@ -1,104 +0,0 @@ -package emu.grasscutter.data.custom; - -import emu.grasscutter.game.quest.enums.LogicType; -import emu.grasscutter.game.quest.enums.QuestTriggerType; -import emu.grasscutter.game.quest.enums.QuestType; - -public class QuestConfigData { - private int id; - private int series; - private QuestType type; - - private long titleTextMapHash; - private int[] suggestTrackMainQuestList; - private int[] rewardIdList; - - private SubQuestConfigData[] subQuests; - - public int getId() { - return id; - } - - public int getSeries() { - return series; - } - - public QuestType getType() { - return type; - } - - public long getTitleTextMapHash() { - return titleTextMapHash; - } - - public int[] getSuggestTrackMainQuestList() { - return suggestTrackMainQuestList; - } - - public int[] getRewardIdList() { - return rewardIdList; - } - - public SubQuestConfigData[] getSubQuests() { - return subQuests; - } - - public class SubQuestConfigData { - private int subId; - private int mainId; - - private LogicType acceptCondComb; - private QuestCondition[] acceptCond; - - private LogicType finishCondComb; - private QuestCondition[] finishCond; - - private LogicType failCondComb; - private QuestCondition[] failCond; - - public int getSubId() { - return subId; - } - - public int getMainId() { - return mainId; - } - - public LogicType getAcceptCondComb() { - return acceptCondComb; - } - - public QuestCondition[] getAcceptCond() { - return acceptCond; - } - - public LogicType getFinishCondComb() { - return finishCondComb; - } - - public QuestCondition[] getFinishCond() { - return finishCond; - } - - public LogicType getFailCondComb() { - return failCondComb; - } - - public QuestCondition[] getFailCond() { - return failCond; - } - } - - public class QuestCondition { - private QuestTriggerType type; - private int[] param; - - public QuestTriggerType getType() { - return type; - } - - public int[] getParam() { - return param; - } - } -} diff --git a/src/main/java/emu/grasscutter/data/def/QuestData.java b/src/main/java/emu/grasscutter/data/def/QuestData.java new file mode 100644 index 000000000..31ac2ce7e --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/QuestData.java @@ -0,0 +1,115 @@ +package emu.grasscutter.data.def; + +import java.util.Arrays; +import java.util.List; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; +import emu.grasscutter.game.quest.enums.LogicType; +import emu.grasscutter.game.quest.enums.QuestTrigger; + +@ResourceType(name = "QuestExcelConfigData.json") +public class QuestData extends GameResource { + private int SubId; + private int MainId; + private int Order; + private long DescTextMapHash; + + private LogicType AcceptCondComb; + private QuestCondition[] acceptConditons; + private LogicType FinishCondComb; + private QuestCondition[] finishConditons; + private LogicType FailCondComb; + private QuestCondition[] failConditons; + + private List AcceptCond; + private List FinishCond; + private List FailCond; + private List BeginExec; + private List FinishExec; + private List FailExec; + + public int getId() { + return SubId; + } + + public int getMainId() { + return MainId; + } + + public int getOrder() { + return Order; + } + + public long getDescTextMapHash() { + return DescTextMapHash; + } + + public LogicType getAcceptCondComb() { + return AcceptCondComb; + } + + public QuestCondition[] getAcceptCond() { + return acceptConditons; + } + + public LogicType getFinishCondComb() { + return FinishCondComb; + } + + public QuestCondition[] getFinishCond() { + return finishConditons; + } + + public LogicType getFailCondComb() { + return FailCondComb; + } + + public QuestCondition[] getFailCond() { + return failConditons; + } + + public void onLoad() { + this.acceptConditons = AcceptCond.stream().filter(p -> p.Type != null).map(QuestCondition::new).toArray(QuestCondition[]::new); + AcceptCond = null; + this.finishConditons = FinishCond.stream().filter(p -> p.Type != null).map(QuestCondition::new).toArray(QuestCondition[]::new); + FinishCond = null; + this.failConditons = FailCond.stream().filter(p -> p.Type != null).map(QuestCondition::new).toArray(QuestCondition[]::new); + FailCond = null; + } + + public class QuestParam { + QuestTrigger Type; + int[] Param; + String count; + } + + public class QuestExecParam { + QuestTrigger Type; + String[] Param; + String count; + } + + public static class QuestCondition { + private QuestTrigger type; + private int[] param; + private String count; + + public QuestCondition(QuestParam param) { + this.type = param.Type; + this.param = param.Param; + } + + public QuestTrigger getType() { + return type; + } + + public int[] getParam() { + return param; + } + + public String getCount() { + return count; + } + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java index 1ceda3356..bf88b8efe 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java @@ -10,7 +10,6 @@ import dev.morphia.annotations.Id; import dev.morphia.annotations.Indexed; import dev.morphia.annotations.Transient; import emu.grasscutter.data.GameData; -import emu.grasscutter.data.custom.QuestConfig; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.quest.enums.ParentQuestState; diff --git a/src/main/java/emu/grasscutter/game/quest/GameQuest.java b/src/main/java/emu/grasscutter/game/quest/GameQuest.java index d3a240a07..b242166eb 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameQuest.java @@ -2,9 +2,11 @@ package emu.grasscutter.game.quest; import dev.morphia.annotations.Entity; import dev.morphia.annotations.Transient; -import emu.grasscutter.data.custom.QuestConfig; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; -import emu.grasscutter.data.custom.QuestConfigData.SubQuestConfigData; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.custom.MainQuestData; +import emu.grasscutter.data.custom.MainQuestData.SubQuestData; +import emu.grasscutter.data.def.QuestData; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.quest.enums.LogicType; import emu.grasscutter.game.quest.enums.QuestState; @@ -16,7 +18,7 @@ import emu.grasscutter.utils.Utils; @Entity public class GameQuest { @Transient private GameMainQuest mainQuest; - @Transient private QuestConfig config; + @Transient private QuestData questData; private int questId; private int mainQuestId; @@ -32,21 +34,21 @@ public class GameQuest { @Deprecated // Morphia only. Do not use. public GameQuest() {} - public GameQuest(GameMainQuest mainQuest, QuestConfig config) { + public GameQuest(GameMainQuest mainQuest, QuestData questData) { this.mainQuest = mainQuest; - this.questId = config.getId(); - this.mainQuestId = config.getMainQuest().getId(); - this.config = config; + this.questId = questData.getId(); + this.mainQuestId = questData.getMainId(); + this.questData = questData; this.acceptTime = Utils.getCurrentSeconds(); this.startTime = this.acceptTime; this.state = QuestState.QUEST_STATE_UNFINISHED; - if (config.getSubQuest().getFinishCond() != null) { - this.finishProgressList = new int[config.getSubQuest().getFinishCond().length]; + if (questData.getFinishCond()!= null) { + this.finishProgressList = new int[questData.getFinishCond().length]; } - if (config.getSubQuest().getFailCond() != null) { - this.failProgressList = new int[config.getSubQuest().getFailCond().length]; + if (questData.getFailCond() != null) { + this.failProgressList = new int[questData.getFailCond().length]; } this.mainQuest.getChildQuests().put(this.questId, this); @@ -72,13 +74,13 @@ public class GameQuest { return mainQuestId; } - public QuestConfig getConfig() { - return config; + public QuestData getData() { + return questData; } - public void setConfig(QuestConfig config) { + public void setConfig(QuestData config) { if (this.getQuestId() != config.getId()) return; - this.config = config; + this.questData = config; } public QuestState getState() { @@ -148,16 +150,23 @@ public class GameQuest { public boolean tryAcceptQuestLine() { try { - for (SubQuestConfigData questData : getConfig().getMainQuest().getSubQuests()) { - GameQuest quest = getMainQuest().getChildQuestById(questData.getSubId()); + MainQuestData questConfig = GameData.getMainQuestDataMap().get(this.getMainQuestId()); + for (SubQuestData subQuest : questConfig.getSubQuests()) { + GameQuest quest = getMainQuest().getChildQuestById(subQuest.getSubId()); if (quest == null) { + QuestData questData = GameData.getQuestDataMap().get(subQuest.getSubId()); + + if (questData == null) { + continue; + } + int[] accept = new int[questData.getAcceptCond().length]; // TODO for (int i = 0; i < questData.getAcceptCond().length; i++) { QuestCondition condition = questData.getAcceptCond()[i]; - boolean result = getOwner().getServer().getQuestHandler().triggerCondition(this, condition); + boolean result = getOwner().getServer().getQuestHandler().triggerCondition(this, condition, condition.getParam()); accept[i] = result ? 1 : 0; } @@ -165,7 +174,7 @@ public class GameQuest { boolean shouldAccept = LogicType.calculate(questData.getAcceptCondComb(), accept); if (shouldAccept) { - this.getOwner().getQuestManager().addQuest(questData.getSubId()); + this.getOwner().getQuestManager().addQuest(questData.getId()); } } } diff --git a/src/main/java/emu/grasscutter/game/quest/QuestManager.java b/src/main/java/emu/grasscutter/game/quest/QuestManager.java index 76c098b07..548e8241a 100644 --- a/src/main/java/emu/grasscutter/game/quest/QuestManager.java +++ b/src/main/java/emu/grasscutter/game/quest/QuestManager.java @@ -9,12 +9,11 @@ import java.util.function.Consumer; import java.util.function.Function; import emu.grasscutter.data.GameData; -import emu.grasscutter.data.custom.QuestConfig; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; -import emu.grasscutter.data.custom.QuestConfigData.SubQuestConfigData; +import emu.grasscutter.data.def.QuestData; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.quest.enums.LogicType; import emu.grasscutter.game.quest.enums.QuestState; import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify; @@ -46,12 +45,12 @@ public class QuestManager { } public GameQuest getQuestById(int questId) { - QuestConfig questConfig = GameData.getQuestConfigs().get(questId); + QuestData questConfig = GameData.getQuestDataMap().get(questId); if (questConfig == null) { return null; } - GameMainQuest mainQuest = getQuests().get(questConfig.getMainQuest().getId()); + GameMainQuest mainQuest = getQuests().get(questConfig.getMainId()); if (mainQuest == null) { return null; @@ -79,8 +78,8 @@ public class QuestManager { } } - public GameMainQuest addMainQuest(QuestConfig questConfig) { - GameMainQuest mainQuest = new GameMainQuest(getPlayer(), questConfig.getMainQuest().getId()); + public GameMainQuest addMainQuest(QuestData questConfig) { + GameMainQuest mainQuest = new GameMainQuest(getPlayer(), questConfig.getMainId()); getQuests().put(mainQuest.getParentQuestId(), mainQuest); getPlayer().sendPacket(new PacketFinishedParentQuestUpdateNotify(mainQuest)); @@ -89,13 +88,13 @@ public class QuestManager { } public GameQuest addQuest(int questId) { - QuestConfig questConfig = GameData.getQuestConfigs().get(questId); + QuestData questConfig = GameData.getQuestDataMap().get(questId); if (questConfig == null) { return null; } // Main quest - GameMainQuest mainQuest = this.getMainQuestById(questConfig.getMainQuest().getId()); + GameMainQuest mainQuest = this.getMainQuestById(questConfig.getMainId()); // Create main quest if it doesnt exist if (mainQuest == null) { @@ -122,11 +121,11 @@ public class QuestManager { return quest; } - public void triggerEvent(QuestTriggerType condType, int... params) { + public void triggerEvent(QuestTrigger condType, int... params) { Set changedQuests = new HashSet<>(); this.forEachActiveQuest(quest -> { - SubQuestConfigData data = quest.getConfig().getSubQuest(); + QuestData data = quest.getData(); for (int i = 0; i < data.getFinishCond().length; i++) { if (quest.getFinishProgressList()[i] == 1) { @@ -150,7 +149,7 @@ public class QuestManager { }); for (GameQuest quest : changedQuests) { - LogicType logicType = quest.getConfig().getSubQuest().getFailCondComb(); + LogicType logicType = quest.getData().getFailCondComb(); int[] progress = quest.getFinishProgressList(); // Handle logical comb @@ -174,7 +173,7 @@ public class QuestManager { for (GameQuest quest : mainQuest.getChildQuests().values()) { quest.setMainQuest(mainQuest); - quest.setConfig(GameData.getQuestConfigs().get(quest.getQuestId())); + quest.setConfig(GameData.getQuestDataMap().get(quest.getQuestId())); } this.getQuests().put(mainQuest.getParentQuestId(), mainQuest); diff --git a/src/main/java/emu/grasscutter/game/quest/QuestValue.java b/src/main/java/emu/grasscutter/game/quest/QuestValue.java index 3042ad5de..42b868fc8 100644 --- a/src/main/java/emu/grasscutter/game/quest/QuestValue.java +++ b/src/main/java/emu/grasscutter/game/quest/QuestValue.java @@ -3,9 +3,9 @@ package emu.grasscutter.game.quest; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; @Retention(RetentionPolicy.RUNTIME) public @interface QuestValue { - QuestTriggerType value(); + QuestTrigger value(); } diff --git a/src/main/java/emu/grasscutter/game/quest/ServerQuestHandler.java b/src/main/java/emu/grasscutter/game/quest/ServerQuestHandler.java index 1c269de90..36c929ab3 100644 --- a/src/main/java/emu/grasscutter/game/quest/ServerQuestHandler.java +++ b/src/main/java/emu/grasscutter/game/quest/ServerQuestHandler.java @@ -4,11 +4,9 @@ import java.util.Set; import org.reflections.Reflections; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.quest.handlers.QuestBaseHandler; -import emu.grasscutter.net.packet.Opcodes; -import emu.grasscutter.server.game.GameServer; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; @@ -62,7 +60,7 @@ public class ServerQuestHandler { public boolean triggerCondition(GameQuest quest, QuestCondition condition, int... params) { QuestBaseHandler handler = condHandlers.get(condition.getType().getValue()); - if (handler == null || quest.getConfig() == null) { + if (handler == null || quest.getData() == null) { return false; } @@ -72,7 +70,7 @@ public class ServerQuestHandler { public boolean triggerContent(GameQuest quest, QuestCondition condition, int... params) { QuestBaseHandler handler = contHandlers.get(condition.getType().getValue()); - if (handler == null || quest.getConfig() == null) { + if (handler == null || quest.getData() == null) { return false; } @@ -82,7 +80,7 @@ public class ServerQuestHandler { public boolean triggerExec(GameQuest quest, QuestCondition condition, int... params) { QuestBaseHandler handler = execHandlers.get(condition.getType().getValue()); - if (handler == null || quest.getConfig() == null) { + if (handler == null || quest.getData() == null) { return false; } diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/BaseCondition.java b/src/main/java/emu/grasscutter/game/quest/conditions/BaseCondition.java index 903773f0e..d94e60c22 100644 --- a/src/main/java/emu/grasscutter/game/quest/conditions/BaseCondition.java +++ b/src/main/java/emu/grasscutter/game/quest/conditions/BaseCondition.java @@ -1,12 +1,12 @@ package emu.grasscutter.game.quest.conditions; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.quest.QuestValue; import emu.grasscutter.game.quest.GameQuest; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.quest.handlers.QuestBaseHandler; -@QuestValue(QuestTriggerType.QUEST_CONTENT_NONE) +@QuestValue(QuestTrigger.QUEST_CONTENT_NONE) public class BaseCondition extends QuestBaseHandler { @Override diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPlayerLevelEqualGreater.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPlayerLevelEqualGreater.java index f5df2b13c..3e3db87fb 100644 --- a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPlayerLevelEqualGreater.java +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPlayerLevelEqualGreater.java @@ -1,17 +1,17 @@ package emu.grasscutter.game.quest.conditions; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.quest.QuestValue; import emu.grasscutter.game.quest.GameQuest; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.quest.handlers.QuestBaseHandler; -@QuestValue(QuestTriggerType.QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER) +@QuestValue(QuestTrigger.QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER) public class ConditionPlayerLevelEqualGreater extends QuestBaseHandler { @Override public boolean execute(GameQuest quest, QuestCondition condition, int... params) { - return quest.getOwner().getLevel() >= condition.getParam()[0]; + return quest.getOwner().getLevel() >= params[0]; } } diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java index 71b44c967..37ecc6d30 100644 --- a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java @@ -1,20 +1,20 @@ package emu.grasscutter.game.quest.conditions; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.quest.QuestValue; import emu.grasscutter.game.quest.GameQuest; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.quest.handlers.QuestBaseHandler; -@QuestValue(QuestTriggerType.QUEST_COND_STATE_EQUAL) +@QuestValue(QuestTrigger.QUEST_COND_STATE_EQUAL) public class ConditionStateEqual extends QuestBaseHandler { @Override public boolean execute(GameQuest quest, QuestCondition condition, int... params) { - GameQuest checkQuest = quest.getOwner().getQuestManager().getQuestById(condition.getParam()[0]); + GameQuest checkQuest = quest.getOwner().getQuestManager().getQuestById(params[0]); if (checkQuest != null) { - return checkQuest.getState().getValue() == condition.getParam()[1]; + return checkQuest.getState().getValue() == params[1]; } return false; diff --git a/src/main/java/emu/grasscutter/game/quest/content/BaseContent.java b/src/main/java/emu/grasscutter/game/quest/content/BaseContent.java index 820d6f133..ce700896d 100644 --- a/src/main/java/emu/grasscutter/game/quest/content/BaseContent.java +++ b/src/main/java/emu/grasscutter/game/quest/content/BaseContent.java @@ -1,12 +1,12 @@ package emu.grasscutter.game.quest.content; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.quest.QuestValue; import emu.grasscutter.game.quest.GameQuest; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.quest.handlers.QuestBaseHandler; -@QuestValue(QuestTriggerType.QUEST_CONTENT_NONE) +@QuestValue(QuestTrigger.QUEST_CONTENT_NONE) public class BaseContent extends QuestBaseHandler { @Override diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java b/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java index aad196306..3423519ec 100644 --- a/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java @@ -1,12 +1,12 @@ package emu.grasscutter.game.quest.content; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.quest.QuestValue; import emu.grasscutter.game.quest.GameQuest; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.quest.handlers.QuestBaseHandler; -@QuestValue(QuestTriggerType.QUEST_CONTENT_COMPLETE_TALK) +@QuestValue(QuestTrigger.QUEST_CONTENT_COMPLETE_TALK) public class ContentCompleteTalk extends QuestBaseHandler { @Override diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestTriggerType.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestTrigger.java similarity index 99% rename from src/main/java/emu/grasscutter/game/quest/enums/QuestTriggerType.java rename to src/main/java/emu/grasscutter/game/quest/enums/QuestTrigger.java index cf8dabdba..def3a399d 100644 --- a/src/main/java/emu/grasscutter/game/quest/enums/QuestTriggerType.java +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestTrigger.java @@ -1,6 +1,6 @@ package emu.grasscutter.game.quest.enums; -public enum QuestTriggerType { +public enum QuestTrigger { QUEST_COND_NONE (0), QUEST_COND_STATE_EQUAL (1), QUEST_COND_STATE_NOT_EQUAL (2), @@ -225,7 +225,7 @@ public enum QuestTriggerType { private final int value; - QuestTriggerType(int id) { + QuestTrigger(int id) { this.value = id; } diff --git a/src/main/java/emu/grasscutter/game/quest/handlers/QuestBaseHandler.java b/src/main/java/emu/grasscutter/game/quest/handlers/QuestBaseHandler.java index 68bf88361..5a3514200 100644 --- a/src/main/java/emu/grasscutter/game/quest/handlers/QuestBaseHandler.java +++ b/src/main/java/emu/grasscutter/game/quest/handlers/QuestBaseHandler.java @@ -1,6 +1,6 @@ package emu.grasscutter.game.quest.handlers; -import emu.grasscutter.data.custom.QuestConfigData.QuestCondition; +import emu.grasscutter.data.def.QuestData.QuestCondition; import emu.grasscutter.game.quest.GameQuest; public abstract class QuestBaseHandler { diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java index 515552289..82248c98c 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java @@ -1,7 +1,7 @@ package emu.grasscutter.server.packet.recv; import emu.grasscutter.game.inventory.GameItem; -import emu.grasscutter.game.quest.enums.QuestTriggerType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.NpcTalkReqOuterClass.NpcTalkReq; @@ -16,7 +16,7 @@ public class HandlerNpcTalkReq extends PacketHandler { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { NpcTalkReq req = NpcTalkReq.parseFrom(payload); - session.getPlayer().getQuestManager().triggerEvent(QuestTriggerType.QUEST_CONTENT_COMPLETE_TALK, req.getTalkId()); + session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_COMPLETE_TALK, req.getTalkId()); session.send(new PacketNpcTalkRsp(req.getNpcEntityId(), req.getTalkId(), req.getEntityId())); } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java index b2ea3d577..fa2e8ab81 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java @@ -1,9 +1,6 @@ package emu.grasscutter.server.packet.send; -import emu.grasscutter.data.GameData; -import emu.grasscutter.data.custom.QuestConfig; import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.quest.GameMainQuest; import emu.grasscutter.game.quest.GameQuest; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; diff --git a/src/main/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index 8b28c027e..5b0f563ee 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -19,10 +19,11 @@ import emu.grasscutter.command.Command; import emu.grasscutter.command.CommandMap; import emu.grasscutter.data.GameData; import emu.grasscutter.data.ResourceLoader; -import emu.grasscutter.data.custom.QuestConfig; +import emu.grasscutter.data.custom.MainQuestData; import emu.grasscutter.data.def.AvatarData; import emu.grasscutter.data.def.ItemData; import emu.grasscutter.data.def.MonsterData; +import emu.grasscutter.data.def.QuestData; import emu.grasscutter.data.def.SceneData; import emu.grasscutter.utils.Utils; @@ -149,13 +150,16 @@ final class ToolsWithLanguageOption { writer.println(data.getId() + " : " + data.getScriptData()); } + writer.println(); + writer.println("// Quests"); - list = new ArrayList<>(GameData.getQuestConfigs().keySet()); + list = new ArrayList<>(GameData.getQuestDataMap().keySet()); Collections.sort(list); for (Integer id : list) { - QuestConfig data = GameData.getQuestConfigs().get(id); - writer.println(data.getId() + " : " + map.get(data.getMainQuest().getTitleTextMapHash())); + QuestData data = GameData.getQuestDataMap().get(id); + MainQuestData mainQuest = GameData.getMainQuestDataMap().get(data.getMainId()); + writer.println(data.getId() + " : " + map.get(mainQuest.getTitleTextMapHash()) + " - " + map.get(data.getDescTextMapHash())); } writer.println(); From cbd46e9215fb02ac625697dff84a6c35dbd42812 Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 13 May 2022 05:33:43 -0700 Subject: [PATCH 37/59] Add one more quest trigger --- .../game/dungeons/DungeonManager.java | 4 +++- .../game/quest/content/ContentEnterDungeon.java | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java index 0a68e6ab0..5c0d1fd27 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java @@ -7,6 +7,7 @@ import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.data.def.DungeonData; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.SceneType; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.server.game.GameServer; @@ -51,8 +52,9 @@ public class DungeonManager { int sceneId = data.getSceneId(); player.getScene().setPrevScene(sceneId); - if(player.getWorld().transferPlayerToScene(player, sceneId, data)){ + if (player.getWorld().transferPlayerToScene(player, sceneId, data)) { player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver); + player.getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON, data.getId()); } player.getScene().setPrevScenePoint(pointId); diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java b/src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java new file mode 100644 index 000000000..e00e59f9a --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java @@ -0,0 +1,17 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.data.def.QuestData.QuestCondition; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON) +public class ContentEnterDungeon extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestCondition condition, int... params) { + return condition.getParam()[0] == params[0]; + } + +} From f83f13204ee53bafbf6ad4f6a60b6c0e24379ccc Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Fri, 6 May 2022 23:39:45 +0930 Subject: [PATCH 38/59] Gacha rework Add fallback stripping and C6 stripping Converting banner definitions from pity vars to lerp arrays Properly implement rates and pool smoothing Also move reusable functions to Utils --- data/Banners.json | 19 +- .../grasscutter/game/gacha/GachaBanner.java | 82 +++-- .../grasscutter/game/gacha/GachaManager.java | 314 ++++++++++-------- .../game/gacha/PlayerGachaBannerInfo.java | 84 ++++- .../java/emu/grasscutter/utils/Utils.java | 66 ++++ 5 files changed, 397 insertions(+), 168 deletions(-) diff --git a/data/Banners.json b/data/Banners.json index a4f724ac9..1aaf39cb7 100644 --- a/data/Banners.json +++ b/data/Banners.json @@ -10,8 +10,9 @@ "beginTime": 0, "endTime": 1924992000, "sortId": 1000, - "rateUpItems1": [], - "rateUpItems2": [] + "fallbackItems4Pool1": [1006, 1014, 1015, 1020, 1021, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064], + "weights4": [[1,510], [8,510], [10,10000]], + "weights5": [[1,75], [73,150], [90,10000]] }, { "gachaType": 301, @@ -24,9 +25,10 @@ "beginTime": 0, "endTime": 1924992000, "sortId": 9998, - "maxItemType": 1, - "rateUpItems1": [1002], - "rateUpItems2": [1053, 1020, 1045] + "rateUpItems4": [1053, 1020, 1045], + "rateUpItems5": [1002], + "fallbackItems5Pool2": [], + "weights5": [[1,80], [73,80], [90,10000]] }, { "gachaType": 302, @@ -39,11 +41,12 @@ "beginTime": 0, "endTime": 1924992000, "sortId": 9997, - "minItemType": 2, "eventChance": 75, "softPity": 80, "hardPity": 80, - "rateUpItems1": [11509, 12504], - "rateUpItems2": [11401, 12402, 13407, 14401, 15401] + "rateUpItems4": [11401, 12402, 13407, 14401, 15401], + "rateUpItems5": [11509, 12504], + "fallbackItems5Pool1": [], + "weights5": [[1,100], [62,100], [73, 7800], [80,10000]] } ] diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index 7a2646a4f..52630768c 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -2,6 +2,7 @@ package emu.grasscutter.game.gacha; import emu.grasscutter.net.proto.GachaInfoOuterClass.GachaInfo; import emu.grasscutter.net.proto.GachaUpInfoOuterClass.GachaUpInfo; +import emu.grasscutter.utils.Utils; import static emu.grasscutter.Configuration.*; @@ -15,14 +16,31 @@ public class GachaBanner { private int beginTime; private int endTime; private int sortId; - private int[] rateUpItems1; - private int[] rateUpItems2; - private int baseYellowWeight = 60; // Max 10000 - private int basePurpleWeight = 510; // Max 10000 - private int eventChance = 50; // Chance to win a featured event item - private int softPity = 75; - private int hardPity = 90; + private int[] rateUpItems4 = {}; + private int[] rateUpItems5 = {}; + private int[] fallbackItems3 = {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304}; + private int[] fallbackItems4Pool1 = {1014, 1020, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064}; + private int[] fallbackItems4Pool2 = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405}; + private int[] fallbackItems5Pool1 = {1003, 1016, 1042, 1035, 1041}; + private int[] fallbackItems5Pool2 = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502}; + private boolean removeC6FromPool = false; + private boolean autoStripRateUpFromFallback = true; + private int[][] weights4 = {{1,510}, {8,510}, {10,10000}}; + private int[][] weights5 = {{1,75}, {73,150}, {90,10000}}; + private int[][] poolBalanceWeights4 = {{1,255}, {17,255}, {21,10455}}; + private int[][] poolBalanceWeights5 = {{1,30}, {147,150}, {181,10230}}; + private int eventChance4 = 50; // Chance to win a featured event item + private int eventChance5 = 50; // Chance to win a featured event item private BannerType bannerType = BannerType.STANDARD; + + // Kinda wanna deprecate these but they're in people's configs + private int[] rateUpItems1 = {}; + private int[] rateUpItems2 = {}; + private int softPity = -1; + private int hardPity = -1; + private int eventChance = -1; + private int baseYellowWeight = -1; + private int basePurpleWeight = -1; public int getGachaType() { return gachaType; @@ -72,24 +90,42 @@ public class GachaBanner { return basePurpleWeight; } - public int[] getRateUpItems1() { - return rateUpItems1; + public int[] getRateUpItems4() { + return (rateUpItems2.length > 0) ? rateUpItems2 : rateUpItems4; + } + public int[] getRateUpItems5() { + return (rateUpItems1.length > 0) ? rateUpItems1 : rateUpItems5; } - public int[] getRateUpItems2() { - return rateUpItems2; - } - - public int getSoftPity() { - return softPity - 1; + public int[] getFallbackItems3() {return fallbackItems3;} + public int[] getFallbackItems4Pool1() {return fallbackItems4Pool1;} + public int[] getFallbackItems4Pool2() {return fallbackItems4Pool2;} + public int[] getFallbackItems5Pool1() {return fallbackItems5Pool1;} + public int[] getFallbackItems5Pool2() {return fallbackItems5Pool2;} + + public boolean getRemoveC6FromPool() {return removeC6FromPool;} + public boolean getAutoStripRateUpFromFallback() {return autoStripRateUpFromFallback;} + + + public int getWeight(int rarity, int pity) { + return switch(rarity) { + case 4 -> Utils.lerp(pity, weights4); + default -> Utils.lerp(pity, weights5); + }; } - public int getHardPity() { - return hardPity - 1; + public int getPoolBalanceWeight(int rarity, int pity) { + return switch(rarity) { + case 4 -> Utils.lerp(pity, poolBalanceWeights4); + default -> Utils.lerp(pity, poolBalanceWeights5); + }; } - public int getEventChance() { - return eventChance; + public int getEventChance(int rarity) { + return switch(rarity) { + case 4 -> eventChance4; + default -> (eventChance > -1) ? eventChance : eventChance5; + }; } @Deprecated @@ -131,10 +167,10 @@ public class GachaBanner { info.setGachaTitlePath(this.getTitlePath()); } - if (this.getRateUpItems1().length > 0) { + if (this.getRateUpItems5().length > 0) { GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(1); - for (int id : getRateUpItems1()) { + for (int id : getRateUpItems5()) { upInfo.addItemIdList(id); info.addMainNameId(id); } @@ -142,10 +178,10 @@ public class GachaBanner { info.addGachaUpInfoList(upInfo); } - if (this.getRateUpItems2().length > 0) { + if (this.getRateUpItems4().length > 0) { GachaUpInfo.Builder upInfo = GachaUpInfo.newBuilder().setItemParentType(2); - for (int id : getRateUpItems2()) { + for (int id : getRateUpItems4()) { upInfo.addItemIdList(id); if (info.getSubNameIdCount() == 0) { info.addSubNameId(id); diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index 160377913..e3c0153e0 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -29,6 +29,7 @@ import emu.grasscutter.net.proto.ItemParamOuterClass.ItemParam; import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServerTickEvent; import emu.grasscutter.server.packet.send.PacketDoGachaRsp; +import emu.grasscutter.utils.Utils; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArrayList; @@ -42,12 +43,6 @@ public class GachaManager { private final Int2ObjectMap gachaBanners; private GetGachaInfoRsp cachedProto; WatchService watchService; - - private final int[] yellowAvatars = new int[] {1003, 1016, 1042, 1035, 1041}; - private final int[] yellowWeapons = new int[] {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502}; - private final int[] purpleAvatars = new int[] {1006, 1014, 1015, 1020, 1021, 1023, 1024, 1025, 1027, 1031, 1032, 1034, 1036, 1039, 1043, 1044, 1045, 1048, 1053, 1055, 1056, 1064}; - private final int[] purpleWeapons = new int[] {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405}; - private final int[] blueWeapons = new int[] {11301, 11302, 11306, 12301, 12302, 12305, 13303, 14301, 14302, 14304, 15301, 15302, 15304}; private static final int starglitterId = 221; private static final int stardustId = 222; @@ -66,24 +61,8 @@ public class GachaManager { public Int2ObjectMap getGachaBanners() { return gachaBanners; } - - public int[] getYellowAvatars() { - return this.yellowAvatars; - } - public int[] getYellowWeapons() { - return this.yellowWeapons; - } - public int[] getPurpleAvatars() { - return this.purpleAvatars; - } - public int[] getPurpleWeapons() { - return this.purpleWeapons; - } - public int[] getBlueWeapons() { - return this.blueWeapons; - } - - public int randomRange(int min, int max) { + + public int randomRange(int min, int max) { // Both are inclusive return ThreadLocalRandom.current().nextInt(max - min + 1) + min; } @@ -98,8 +77,14 @@ public class GachaManager { if(banners.size() > 0) { for (GachaBanner banner : banners) { getGachaBanners().put(banner.getGachaType(), banner); + Grasscutter.getLogger().info(String.format("Testing lerp code for banner gachaType %d :", banner.getGachaType())); // TODO: remove this before merging! + for (int i=1; i<91; i++) { + Grasscutter.getLogger().info(String.format("Pity %d : Weight %d", i, banner.getWeight(5, i))); + } } Grasscutter.getLogger().info("Banners successfully loaded."); + + this.cachedProto = createProto(); } else { Grasscutter.getLogger().error("Unable to load banners. Banners size is 0."); @@ -109,6 +94,139 @@ public class GachaManager { e.printStackTrace(); } } + + private class BannerPools { + public int[] rateUpItems4; + public int[] rateUpItems5; + public int[] fallbackItems4Pool1; + public int[] fallbackItems4Pool2; + public int[] fallbackItems5Pool1; + public int[] fallbackItems5Pool2; + + public BannerPools(GachaBanner banner) { + rateUpItems4 = banner.getRateUpItems4(); + rateUpItems5 = banner.getRateUpItems5(); + fallbackItems4Pool1 = banner.getFallbackItems4Pool1(); + fallbackItems4Pool2 = banner.getFallbackItems4Pool2(); + fallbackItems5Pool1 = banner.getFallbackItems5Pool1(); + fallbackItems5Pool2 = banner.getFallbackItems5Pool2(); + + if (banner.getAutoStripRateUpFromFallback()) { + fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, rateUpItems4); + fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, rateUpItems4); + fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, rateUpItems5); + fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, rateUpItems5); + } + } + + public void removeFromAllPools(int[] itemIds) { + rateUpItems4 = Utils.setSubtract(rateUpItems4, itemIds); + rateUpItems5 = Utils.setSubtract(rateUpItems5, itemIds); + fallbackItems4Pool1 = Utils.setSubtract(fallbackItems4Pool1, itemIds); + fallbackItems4Pool2 = Utils.setSubtract(fallbackItems4Pool2, itemIds); + fallbackItems5Pool1 = Utils.setSubtract(fallbackItems5Pool1, itemIds); + fallbackItems5Pool2 = Utils.setSubtract(fallbackItems5Pool2, itemIds); + } + } + + private synchronized int checkPlayerAvatarConstellationLevel(Player player, int itemId) { // Maybe this would be useful in the Player class? + ItemData itemData = GameData.getItemDataMap().get(itemId); + if ((itemData == null) || (itemData.getMaterialType() != MaterialType.MATERIAL_AVATAR)){ + return -2; // Not an Avatar + } + Avatar avatar = player.getAvatars().getAvatarById((itemId % 1000) + 10000000); + if (avatar == null) { + return -1; // Doesn't have + } + // Constellation + int constLevel = avatar.getCoreProudSkillLevel(); + GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(itemId + 100); + constLevel += (constItem == null)? 0 : constItem.getCount(); + return constLevel; + } + + private synchronized int[] removeC6FromPool(int[] itemPool, Player player) { + IntList temp = new IntArrayList(); + for (int itemId : itemPool) { + if (checkPlayerAvatarConstellationLevel(player, itemId) < 6) { + temp.add(itemId); + } + } + return temp.toIntArray(); + } + + private synchronized int drawRoulette(int[] weights, int cutoff) { + // This follows the logic laid out in issue #183 + // Simple weighted selection with an upper bound for the roll that cuts off trailing entries + // All weights must be >= 0 + int total = 0; + for (int i : weights) { + if (i < 0) { + throw new IllegalArgumentException("Weights must be non-negative!"); + } + total += i; + } + int roll = ThreadLocalRandom.current().nextInt((total < cutoff)? total : cutoff); + int subTotal = 0; + for (int i : weights) { + subTotal += i; + if (roll < subTotal) { + return i; + } + } + // throw new IllegalStateException(); + return 0; // This should only be reachable if total==0 + } + + private synchronized int doRarePull(int[] featured, int[] fallback1, int[] fallback2, int rarity, GachaBanner banner, PlayerGachaBannerInfo gachaInfo) { + int itemId = 0; + if ( (featured.length > 0) + && (gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1) + || (this.randomRange(1, 100) <= banner.getEventChance(rarity))) { + itemId = getRandom(featured); + gachaInfo.setFailedFeaturedItemPulls(rarity, 0); + } else { + gachaInfo.addFailedFeaturedItemPulls(rarity, 1); + if (fallback1.length < 1) { + itemId = getRandom(fallback2); // Don't ever run an empty fallback2 btw + } else { + int pityPool1 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 1)); + int pityPool2 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 2)); + int chosenPool = switch ((pityPool1 >= pityPool2)? 1 : 0) { // Larger weight must come first for the hard cutoff to function correctly + case 1 -> 1 + drawRoulette(new int[] {pityPool1, pityPool2}, 10000); + default -> 2 - drawRoulette(new int[] {pityPool2, pityPool1}, 10000); + }; + itemId = switch (chosenPool) { + case 1: + gachaInfo.setPityPool(rarity, 1, 0); + yield getRandom(fallback1); + default: + gachaInfo.setPityPool(rarity, 2, 0); + yield getRandom(fallback2); + }; + } + } + return itemId; + } + + private synchronized int doPull(GachaBanner banner, PlayerGachaBannerInfo gachaInfo, BannerPools pools) { + // Pre-increment all pity pools (yes this makes all calculations assume 1-indexed pity) + gachaInfo.incPityAll(); + + int[] weights = {banner.getWeight(5, gachaInfo.getPity5()), banner.getWeight(4, gachaInfo.getPity4()), 10000}; + int levelWon = 5 - drawRoulette(weights, 10000); + + return switch (levelWon) { + case 5: + gachaInfo.setPity5(0); + yield doRarePull(pools.rateUpItems5, pools.fallbackItems5Pool1, pools.fallbackItems5Pool2, 5, banner, gachaInfo); + case 4: + gachaInfo.setPity4(0); + yield doRarePull(pools.rateUpItems4, pools.fallbackItems4Pool1, pools.fallbackItems4Pool2, 4, banner, gachaInfo); + default: + yield getRandom(banner.getFallbackItems3()); + }; + } public synchronized void doPulls(Player player, int gachaType, int times) { // Sanity check @@ -132,84 +250,27 @@ public class GachaManager { return; } - // Roll - PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner); - IntList wonItems = new IntArrayList(times); - - for (int i = 0; i < times; i++) { - int random = this.randomRange(1, 10000); - int itemId = 0; - - int bonusYellowChance = gachaInfo.getPity5() >= banner.getSoftPity() ? 100 * (gachaInfo.getPity5() - banner.getSoftPity() - 1): 0; - int yellowChance = banner.getBaseYellowWeight() + (int) Math.floor(100f * (gachaInfo.getPity5() / (banner.getSoftPity() - 1D))) + bonusYellowChance; - int purpleChance = 10000 - (banner.getBasePurpleWeight() + (int) Math.floor(790f * (gachaInfo.getPity4() / 8f))); - - if (random <= yellowChance || gachaInfo.getPity5() >= banner.getHardPity()) { - if (banner.getRateUpItems1().length > 0) { - int eventChance = this.randomRange(1, 100); - - if (eventChance <= banner.getEventChance() || gachaInfo.getFailedFeaturedItemPulls() >= 1) { - itemId = getRandom(banner.getRateUpItems1()); - gachaInfo.setFailedFeaturedItemPulls(0); - } else { - // Lost the 50/50... rip - gachaInfo.addFailedFeaturedItemPulls(1); - } - } - - if (itemId == 0) { - int typeChance = this.randomRange(banner.getBannerType() == BannerType.WEAPON ? 2 : 1, banner.getBannerType() == BannerType.EVENT ? 1 : 2); - if (typeChance == 1) { - itemId = getRandom(this.yellowAvatars); - } else { - itemId = getRandom(this.yellowWeapons); - } - } - - // Pity - gachaInfo.addPity4(1); - gachaInfo.setPity5(0); - } else if (random >= purpleChance || gachaInfo.getPity4() >= 9) { - if (banner.getRateUpItems2().length > 0) { - int eventChance = this.randomRange(1, 100); - - if (eventChance >= 50) { - itemId = getRandom(banner.getRateUpItems2()); - } - } - - if (itemId == 0) { - int typeChance = this.randomRange(banner.getBannerType() == BannerType.WEAPON ? 2 : 1, banner.getBannerType() == BannerType.EVENT ? 1 : 2); - if (typeChance == 1) { - itemId = getRandom(this.purpleAvatars); - } else { - itemId = getRandom(this.purpleWeapons); - } - } - - // Pity - gachaInfo.addPity5(1); - gachaInfo.setPity4(0); - } else { - itemId = getRandom(this.blueWeapons); - - // Pity - gachaInfo.addPity4(1); - gachaInfo.addPity5(1); - } - - // Add winning item - wonItems.add(itemId); - } - // Add to character + PlayerGachaBannerInfo gachaInfo = player.getGachaInfo().getBannerInfo(banner); + BannerPools pools = new BannerPools(banner); List list = new ArrayList<>(); int stardust = 0, starglitter = 0; + + if (banner.getRemoveC6FromPool()) { // The ultimate form of pity (non-vanilla) + pools.rateUpItems4 = removeC6FromPool(pools.rateUpItems4, player); + pools.rateUpItems5 = removeC6FromPool(pools.rateUpItems5, player); + pools.fallbackItems4Pool1 = removeC6FromPool(pools.fallbackItems4Pool1, player); + pools.fallbackItems4Pool2 = removeC6FromPool(pools.fallbackItems4Pool2, player); + pools.fallbackItems5Pool1 = removeC6FromPool(pools.fallbackItems5Pool1, player); + pools.fallbackItems5Pool2 = removeC6FromPool(pools.fallbackItems5Pool2, player); + } - for (int itemId : wonItems) { + for (int i = 0; i < times; i++) { + // Roll + int itemId = doPull(banner, gachaInfo, pools); ItemData itemData = GameData.getItemDataMap().get(itemId); if (itemData == null) { - continue; + continue; // Maybe we should bail out if an item fails instead of rolling the rest? } // Write gacha record @@ -222,44 +283,33 @@ public class GachaManager { boolean isTransferItem = false; // Const check - if (itemData.getMaterialType() == MaterialType.MATERIAL_AVATAR) { - int avatarId = (itemData.getId() % 1000) + 10000000; - Avatar avatar = player.getAvatars().getAvatarById(avatarId); - if (avatar != null) { - int constLevel = avatar.getCoreProudSkillLevel(); - int constItemId = itemData.getId() + 100; - GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId); - if (constItem != null) { - constLevel += constItem.getCount(); + int constellation = checkPlayerAvatarConstellationLevel(player, itemId); + switch (constellation) { + case -2: // Is weapon + switch (itemData.getRankLevel()) { + case 5 -> addStarglitter = 10; + case 4 -> addStarglitter = 2; + default -> addStardust = 15; } - - if (constLevel < 6) { - // Not max const - addStarglitter = 2; - // Add 1 const + break; + case -1: // New character + gachaItem.setIsGachaItemNew(true); + break; + default: + if (constellation >= 6) { // C6, give consolation starglitter + addStarglitter = (itemData.getRankLevel()==5)? 25 : 5; + } else { // C0-C5, give constellation item + if (banner.getRemoveC6FromPool() && constellation == 5) { // New C6, remove it from the pools so we don't get C7 in a 10pull + pools.removeFromAllPools(new int[] {itemId}); + } + addStarglitter = (itemData.getRankLevel()==5)? 10 : 2; + int constItemId = itemId + 100; + GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId); gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1)).setIsTransferItemNew(constItem == null)); player.getInventory().addItem(constItemId, 1); - } else { - // Is max const - addStarglitter = 5; } - - if (itemData.getRankLevel() == 5) { - addStarglitter *= 5; - } - isTransferItem = true; - } else { - // New - gachaItem.setIsGachaItemNew(true); - } - } else { - // Is weapon - switch (itemData.getRankLevel()) { - case 5 -> addStarglitter = 10; - case 4 -> addStarglitter = 2; - case 3 -> addStardust = 15; - } + break; } // Create item @@ -272,7 +322,8 @@ public class GachaManager { if (addStardust > 0) { gachaItem.addTokenItemList(ItemParam.newBuilder().setItemId(stardustId).setCount(addStardust)); - } if (addStarglitter > 0) { + } + if (addStarglitter > 0) { ItemParam starglitterParam = ItemParam.newBuilder().setItemId(starglitterId).setCount(addStarglitter).build(); if (isTransferItem) { gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(starglitterParam)); @@ -286,7 +337,8 @@ public class GachaManager { // Add stardust/starglitter if (stardust > 0) { player.getInventory().addItem(stardustId, stardust); - } if (starglitter > 0) { + } + if (starglitter > 0) { player.getInventory().addItem(starglitterId, starglitter); } diff --git a/src/main/java/emu/grasscutter/game/gacha/PlayerGachaBannerInfo.java b/src/main/java/emu/grasscutter/game/gacha/PlayerGachaBannerInfo.java index b0c85d355..f07d2eff0 100644 --- a/src/main/java/emu/grasscutter/game/gacha/PlayerGachaBannerInfo.java +++ b/src/main/java/emu/grasscutter/game/gacha/PlayerGachaBannerInfo.java @@ -7,6 +7,11 @@ public class PlayerGachaBannerInfo { private int pity5 = 0; private int pity4 = 0; private int failedFeaturedItemPulls = 0; + private int failedFeatured4ItemPulls = 0; + private int pity5Pool1 = 0; + private int pity5Pool2 = 0; + private int pity4Pool1 = 0; + private int pity4Pool2 = 0; public int getPity5() { return pity5; @@ -32,15 +37,82 @@ public class PlayerGachaBannerInfo { this.pity4 += amount; } - public int getFailedFeaturedItemPulls() { - return failedFeaturedItemPulls; + public int getFailedFeaturedItemPulls(int rarity) { + return switch (rarity) { + case 4 -> failedFeatured4ItemPulls; + default -> failedFeaturedItemPulls; // 5 + }; } - public void setFailedFeaturedItemPulls(int failedEventCharacterPulls) { - this.failedFeaturedItemPulls = failedEventCharacterPulls; + public void setFailedFeaturedItemPulls(int rarity, int amount) { + switch (rarity) { + case 4 -> failedFeatured4ItemPulls = amount; + default -> failedFeaturedItemPulls = amount; // 5 + }; } - public void addFailedFeaturedItemPulls(int amount) { - failedFeaturedItemPulls += amount; + public void addFailedFeaturedItemPulls(int rarity, int amount) { + switch (rarity) { + case 4 -> failedFeatured4ItemPulls += amount; + default -> failedFeaturedItemPulls += amount; // 5 + }; + } + + public int getPityPool(int rarity, int pool) { + return switch (rarity) { + case 4 -> switch (pool) { + case 1 -> pity4Pool1; + default -> pity4Pool2; + }; + default -> switch (pool) { + case 1 -> pity5Pool1; + default -> pity5Pool2; + }; + }; + } + + public void setPityPool(int rarity, int pool, int amount) { + switch (rarity) { + case 4: + switch (pool) { + case 1 -> pity4Pool1 = amount; + default -> pity4Pool2 = amount; + }; + break; + case 5: + default: + switch (pool) { + case 1 -> pity5Pool1 = amount; + default -> pity5Pool2 = amount; + }; + break; + }; + } + + public void addPityPool(int rarity, int pool, int amount) { + switch (rarity) { + case 4: + switch (pool) { + case 1 -> pity4Pool1 += amount; + default -> pity4Pool2 += amount; + }; + break; + case 5: + default: + switch (pool) { + case 1 -> pity5Pool1 += amount; + default -> pity5Pool2 += amount; + }; + break; + }; + } + + public void incPityAll() { + pity4++; + pity5++; + pity4Pool1++; + pity4Pool2++; + pity5Pool1++; + pity5Pool2++; } } diff --git a/src/main/java/emu/grasscutter/utils/Utils.java b/src/main/java/emu/grasscutter/utils/Utils.java index 4af62bfb4..58fc83cd0 100644 --- a/src/main/java/emu/grasscutter/utils/Utils.java +++ b/src/main/java/emu/grasscutter/utils/Utils.java @@ -15,6 +15,8 @@ import emu.grasscutter.Grasscutter; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; import org.slf4j.Logger; @@ -314,4 +316,68 @@ public final class Utils { return String.format("%s-%s", locale.getLanguage(), locale.getCountry()); } + /** + * Performs a linear interpolation using a table of fixed points to create an effective piecewise f(x) = y function. + * @param x + * @param xyArray Array of points in [[x0,y0], ... [xN, yN]] format + * @return f(x) = y + */ + public static int lerp(int x, int[][] xyArray) { + try { + if (x <= xyArray[0][0]){ // Clamp to first point + return xyArray[0][1]; + } else if (x >= xyArray[xyArray.length-1][0]) { // Clamp to last point + return xyArray[xyArray.length-1][1]; + } + // At this point we're guaranteed to have two lerp points, and pity be somewhere between them. + for (int i=0; i < xyArray.length-1; i++) { + if (x == xyArray[i+1][0]) { + return xyArray[i+1][1]; + } + if (x < xyArray[i+1][0]) { + // We are between [i] and [i+1], interpolation time! + // Using floats would be slightly cleaner but we can just as easily use ints if we're careful with order of operations. + int position = x - xyArray[i][0]; + int fullDist = xyArray[i+1][0] - xyArray[i][0]; + int prevValue = xyArray[i][1]; + int fullDelta = xyArray[i+1][1] - prevValue; + return prevValue + ( (position * fullDelta) / fullDist ); + } + } + } catch (IndexOutOfBoundsException e) { + Grasscutter.getLogger().error("Malformed lerp point array. Must be of form [[x0, y0], ..., [xN, yN]]."); + } + return 0; + } + + /** + * Checks if an int is in an int[] + * @param key int to look for + * @param array int[] to look in + * @return key in array + */ + public static boolean intInArray(int key, int[] array) { + for (int i : array) { + if (i == key) { + return true; + } + } + return false; + } + + /** + * Return a copy of minuend without any elements found in subtrahend. + * @param minuend The array we want elements from + * @param subtrahend The array whose elements we don't want + * @return The array with only the elements we want, in the order that minuend had them + */ + public static int[] setSubtract(int[] minuend, int[] subtrahend) { + IntList temp = new IntArrayList(); + for (int i : minuend) { + if (!intInArray(i, subtrahend)) { + temp.add(i); + } + } + return temp.toIntArray(); + } } From 83c46cb9c8254d2fe84b11f320fe0741e10548a7 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Wed, 11 May 2022 19:24:59 +0930 Subject: [PATCH 39/59] Custom costs for different gacha pulls --- .../grasscutter/game/gacha/GachaBanner.java | 29 ++++++++++--------- .../grasscutter/game/gacha/GachaManager.java | 17 ++++++----- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index 52630768c..3c97e671c 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -6,13 +6,18 @@ import emu.grasscutter.utils.Utils; import static emu.grasscutter.Configuration.*; +import emu.grasscutter.data.common.ItemParamData; + public class GachaBanner { private int gachaType; private int scheduleId; private String prefabPath; private String previewPrefabPath; private String titlePath; - private int costItem; + private int costItemId = 0; + private int costItemAmount = 1; + private int costItemId10 = 0; + private int costItemAmount10 = 10; private int beginTime; private int endTime; private int sortId; @@ -36,11 +41,8 @@ public class GachaBanner { // Kinda wanna deprecate these but they're in people's configs private int[] rateUpItems1 = {}; private int[] rateUpItems2 = {}; - private int softPity = -1; - private int hardPity = -1; private int eventChance = -1; - private int baseYellowWeight = -1; - private int basePurpleWeight = -1; + private int costItem = 0; public int getGachaType() { return gachaType; @@ -66,8 +68,15 @@ public class GachaBanner { return titlePath; } + public ItemParamData getCost(int numRolls) { + return switch (numRolls) { + case 10 -> new ItemParamData((costItemId10 > 0) ? costItemId10 : getCostItem(), costItemAmount10); + default -> new ItemParamData(getCostItem(), costItemAmount * numRolls); + }; + } + public int getCostItem() { - return costItem; + return (costItem > 0) ? costItem : costItemId; } public int getBeginTime() { @@ -82,14 +91,6 @@ public class GachaBanner { return sortId; } - public int getBaseYellowWeight() { - return baseYellowWeight; - } - - public int getBasePurpleWeight() { - return basePurpleWeight; - } - public int[] getRateUpItems4() { return (rateUpItems2.length > 0) ? rateUpItems2 : rateUpItems4; } diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index e3c0153e0..11945c3d3 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -19,6 +19,7 @@ import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.gacha.GachaBanner.BannerType; import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.inventory.ItemType; import emu.grasscutter.game.inventory.MaterialType; import emu.grasscutter.game.player.Player; @@ -233,7 +234,8 @@ public class GachaManager { if (times != 10 && times != 1) { return; } - if (player.getInventory().getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times > player.getInventory().getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) { + Inventory inventory = player.getInventory(); + if (inventory.getInventoryTab(ItemType.ITEM_WEAPON).getSize() + times > inventory.getInventoryTab(ItemType.ITEM_WEAPON).getMaxCapacity()) { player.sendPacket(new PacketDoGachaRsp()); return; } @@ -246,7 +248,8 @@ public class GachaManager { } // Spend currency - if (banner.getCostItem() > 0 && !player.getInventory().payItem(banner.getCostItem(), times)) { + ItemParamData cost = banner.getCost(times); + if (cost.getCount() > 0 && !inventory.payItem(cost)) { return; } @@ -304,9 +307,9 @@ public class GachaManager { } addStarglitter = (itemData.getRankLevel()==5)? 10 : 2; int constItemId = itemId + 100; - GameItem constItem = player.getInventory().getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId); + GameItem constItem = inventory.getInventoryTab(ItemType.ITEM_MATERIAL).getItemById(constItemId); gachaItem.addTransferItems(GachaTransferItem.newBuilder().setItem(ItemParam.newBuilder().setItemId(constItemId).setCount(1)).setIsTransferItemNew(constItem == null)); - player.getInventory().addItem(constItemId, 1); + inventory.addItem(constItemId, 1); } isTransferItem = true; break; @@ -315,7 +318,7 @@ public class GachaManager { // Create item GameItem item = new GameItem(itemData); gachaItem.setGachaItem(item.toItemParam()); - player.getInventory().addItem(item); + inventory.addItem(item); stardust += addStardust; starglitter += addStarglitter; @@ -336,10 +339,10 @@ public class GachaManager { // Add stardust/starglitter if (stardust > 0) { - player.getInventory().addItem(stardustId, stardust); + inventory.addItem(stardustId, stardust); } if (starglitter > 0) { - player.getInventory().addItem(starglitterId, starglitter); + inventory.addItem(starglitterId, starglitter); } // Packets From 736ca85300d79c188d5f779614ee2179f2b241e2 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Thu, 12 May 2022 00:14:26 +0930 Subject: [PATCH 40/59] Remove debug log from gacha --- src/main/java/emu/grasscutter/game/gacha/GachaManager.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index 11945c3d3..a0337be7f 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -17,7 +17,6 @@ import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.data.def.ItemData; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.avatar.Avatar; -import emu.grasscutter.game.gacha.GachaBanner.BannerType; import emu.grasscutter.game.inventory.GameItem; import emu.grasscutter.game.inventory.Inventory; import emu.grasscutter.game.inventory.ItemType; @@ -78,10 +77,6 @@ public class GachaManager { if(banners.size() > 0) { for (GachaBanner banner : banners) { getGachaBanners().put(banner.getGachaType(), banner); - Grasscutter.getLogger().info(String.format("Testing lerp code for banner gachaType %d :", banner.getGachaType())); // TODO: remove this before merging! - for (int i=1; i<91; i++) { - Grasscutter.getLogger().info(String.format("Pity %d : Weight %d", i, banner.getWeight(5, i))); - } } Grasscutter.getLogger().info("Banners successfully loaded."); From 420801b49e2d0f882064b4830fa9ba35cefedff1 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Thu, 12 May 2022 02:23:51 +0930 Subject: [PATCH 41/59] Updated cost logic and default weights --- data/Banners.json | 9 ++++-- .../grasscutter/game/gacha/GachaBanner.java | 12 ++++---- .../grasscutter/game/gacha/GachaManager.java | 30 ++++++++++++------- .../server/packet/send/PacketDoGachaRsp.java | 11 ++++--- 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/data/Banners.json b/data/Banners.json index 1aaf39cb7..17e720e65 100644 --- a/data/Banners.json +++ b/data/Banners.json @@ -6,7 +6,9 @@ "prefabPath": "GachaShowPanel_A022", "previewPrefabPath": "UI_Tab_GachaShowPanel_A022", "titlePath": "UI_GACHA_SHOW_PANEL_A022_TITLE", - "costItem": 224, + "costItemId": 224, + "costItemAmount": 1, + "costItemAmount10": 10, "beginTime": 0, "endTime": 1924992000, "sortId": 1000, @@ -21,7 +23,7 @@ "prefabPath": "GachaShowPanel_A079", "previewPrefabPath": "UI_Tab_GachaShowPanel_A079", "titlePath": "UI_GACHA_SHOW_PANEL_A048_TITLE", - "costItem": 223, + "costItemId": 223, "beginTime": 0, "endTime": 1924992000, "sortId": 9998, @@ -37,7 +39,7 @@ "prefabPath": "GachaShowPanel_A080", "previewPrefabPath": "UI_Tab_GachaShowPanel_A080", "titlePath": "UI_GACHA_SHOW_PANEL_A021_TITLE", - "costItem": 223, + "costItemId": 223, "beginTime": 0, "endTime": 1924992000, "sortId": 9997, @@ -47,6 +49,7 @@ "rateUpItems4": [11401, 12402, 13407, 14401, 15401], "rateUpItems5": [11509, 12504], "fallbackItems5Pool1": [], + "weights4": [[1,600], [7,600], [8, 6600], [10,12600]], "weights5": [[1,100], [62,100], [73, 7800], [80,10000]] } ] diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java index 3c97e671c..1586198c2 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaBanner.java @@ -6,6 +6,7 @@ import emu.grasscutter.utils.Utils; import static emu.grasscutter.Configuration.*; +import emu.grasscutter.Grasscutter; import emu.grasscutter.data.common.ItemParamData; public class GachaBanner { @@ -145,25 +146,26 @@ public class GachaBanner { + "/gacha/details?s=" + sessionKey + "&gachaType=" + gachaType; // Grasscutter.getLogger().info("record = " + record); + ItemParamData costItem1 = this.getCost(1); + ItemParamData costItem10 = this.getCost(10); GachaInfo.Builder info = GachaInfo.newBuilder() .setGachaType(this.getGachaType()) .setScheduleId(this.getScheduleId()) .setBeginTime(this.getBeginTime()) .setEndTime(this.getEndTime()) - .setCostItemId(this.getCostItem()) - .setCostItemNum(1) + .setCostItemId(costItem1.getId()) + .setCostItemNum(costItem1.getCount()) + .setTenCostItemId(costItem10.getId()) + .setTenCostItemNum(costItem10.getCount()) .setGachaPrefabPath(this.getPrefabPath()) .setGachaPreviewPrefabPath(this.getPreviewPrefabPath()) .setGachaProbUrl(details) .setGachaProbUrlOversea(details) .setGachaRecordUrl(record) .setGachaRecordUrlOversea(record) - .setTenCostItemId(this.getCostItem()) - .setTenCostItemNum(10) .setLeftGachaTimes(Integer.MAX_VALUE) .setGachaTimesLimit(Integer.MAX_VALUE) .setGachaSortId(this.getSortId()); - if (this.getTitlePath() != null) { info.setGachaTitlePath(this.getTitlePath()); } diff --git a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java index a0337be7f..4cbfde094 100644 --- a/src/main/java/emu/grasscutter/game/gacha/GachaManager.java +++ b/src/main/java/emu/grasscutter/game/gacha/GachaManager.java @@ -4,6 +4,7 @@ import java.io.File; import java.io.FileReader; import java.nio.file.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.concurrent.ThreadLocalRandom; @@ -46,6 +47,8 @@ public class GachaManager { private static final int starglitterId = 221; private static final int stardustId = 222; + private int[] fallbackItems4Pool2Default = {11401, 11402, 11403, 11405, 12401, 12402, 12403, 12405, 13401, 13407, 14401, 14402, 14403, 14409, 15401, 15402, 15403, 15405}; + private int[] fallbackItems5Pool2Default = {11501, 11502, 12501, 12502, 13502, 13505, 14501, 14502, 15501, 15502}; public GachaManager(GameServer server) { this.server = server; @@ -156,16 +159,16 @@ public class GachaManager { // Simple weighted selection with an upper bound for the roll that cuts off trailing entries // All weights must be >= 0 int total = 0; - for (int i : weights) { - if (i < 0) { + for (int weight : weights) { + if (weight < 0) { throw new IllegalArgumentException("Weights must be non-negative!"); } - total += i; + total += weight; } int roll = ThreadLocalRandom.current().nextInt((total < cutoff)? total : cutoff); int subTotal = 0; - for (int i : weights) { - subTotal += i; + for (int i=0; i 0) - && (gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1) - || (this.randomRange(1, 100) <= banner.getEventChance(rarity))) { + boolean pullFeatured = (gachaInfo.getFailedFeaturedItemPulls(rarity) >= 1) // Lost previous coinflip + || (this.randomRange(1, 100) <= banner.getEventChance(rarity)); // Won this coinflip + if (pullFeatured && (featured.length > 0)) { itemId = getRandom(featured); gachaInfo.setFailedFeaturedItemPulls(rarity, 0); } else { gachaInfo.addFailedFeaturedItemPulls(rarity, 1); if (fallback1.length < 1) { - itemId = getRandom(fallback2); // Don't ever run an empty fallback2 btw - } else { + if (fallback2.length < 1) { + itemId = getRandom((rarity==5)? fallbackItems5Pool2Default : fallbackItems4Pool2Default); + } else { + itemId = getRandom(fallback2); + } + } else if (fallback2.length < 1) { + itemId = getRandom(fallback1); + } else { // Both pools are possible, use the pool balancer int pityPool1 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 1)); int pityPool2 = banner.getPoolBalanceWeight(rarity, gachaInfo.getPityPool(rarity, 2)); int chosenPool = switch ((pityPool1 >= pityPool2)? 1 : 0) { // Larger weight must come first for the hard cutoff to function correctly @@ -245,6 +254,7 @@ public class GachaManager { // Spend currency ItemParamData cost = banner.getCost(times); if (cost.getCount() > 0 && !inventory.payItem(cost)) { + player.sendPacket(new PacketDoGachaRsp()); return; } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketDoGachaRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketDoGachaRsp.java index 9144c0d8e..6d8b9ddd9 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketDoGachaRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketDoGachaRsp.java @@ -2,6 +2,7 @@ package emu.grasscutter.server.packet.send; import java.util.List; +import emu.grasscutter.data.common.ItemParamData; import emu.grasscutter.game.gacha.GachaBanner; import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; @@ -14,16 +15,18 @@ public class PacketDoGachaRsp extends BasePacket { public PacketDoGachaRsp(GachaBanner banner, List list) { super(PacketOpcodes.DoGachaRsp); + ItemParamData costItem = banner.getCost(1); + ItemParamData costItem10 = banner.getCost(10); DoGachaRsp p = DoGachaRsp.newBuilder() .setGachaType(banner.getGachaType()) .setGachaScheduleId(banner.getScheduleId()) .setGachaTimes(list.size()) .setNewGachaRandom(12345) .setLeftGachaTimes(Integer.MAX_VALUE) - .setCostItemId(banner.getCostItem()) - .setCostItemNum(1) - .setTenCostItemId(banner.getCostItem()) - .setTenCostItemNum(10) + .setCostItemId(costItem.getId()) + .setCostItemNum(costItem.getCount()) + .setTenCostItemId(costItem10.getId()) + .setTenCostItemNum(costItem10.getCount()) .addAllGachaItemList(list) .build(); From 395da1ebcb229ac999282a536025808b5dee57a0 Mon Sep 17 00:00:00 2001 From: AnimeGitB Date: Thu, 12 May 2022 15:00:40 +0930 Subject: [PATCH 42/59] Fix gachadetails --- .../dispatch/http/GachaDetailsHandler.java | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java b/src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java index 5e1877b9b..e5359a9da 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java +++ b/src/main/java/emu/grasscutter/server/dispatch/http/GachaDetailsHandler.java @@ -62,28 +62,24 @@ public final class GachaDetailsHandler implements HttpContextHandler { // Add 5-star items. Set fiveStarItems = new LinkedHashSet<>(); - Arrays.stream(banner.getRateUpItems1()).forEach(i -> fiveStarItems.add(Integer.toString(i))); - if (banner.getBannerType() == BannerType.STANDARD || banner.getBannerType() == BannerType.EVENT) { - Arrays.stream(manager.getYellowAvatars()).forEach(i -> fiveStarItems.add(Integer.toString(i))); - } - if (banner.getBannerType() == BannerType.STANDARD || banner.getBannerType() == BannerType.WEAPON) { - Arrays.stream(manager.getYellowWeapons()).forEach(i -> fiveStarItems.add(Integer.toString(i))); - } + Arrays.stream(banner.getRateUpItems5()).forEach(i -> fiveStarItems.add(Integer.toString(i))); + Arrays.stream(banner.getFallbackItems5Pool1()).forEach(i -> fiveStarItems.add(Integer.toString(i))); + Arrays.stream(banner.getFallbackItems5Pool2()).forEach(i -> fiveStarItems.add(Integer.toString(i))); response = response.replace("{{FIVE_STARS}}", "[" + String.join(",", fiveStarItems) + "]"); // Add 4-star items. Set fourStarItems = new LinkedHashSet<>(); - Arrays.stream(banner.getRateUpItems2()).forEach(i -> fourStarItems.add(Integer.toString(i))); - Arrays.stream(manager.getPurpleAvatars()).forEach(i -> fourStarItems.add(Integer.toString(i))); - Arrays.stream(manager.getPurpleWeapons()).forEach(i -> fourStarItems.add(Integer.toString(i))); + Arrays.stream(banner.getRateUpItems4()).forEach(i -> fourStarItems.add(Integer.toString(i))); + Arrays.stream(banner.getFallbackItems4Pool1()).forEach(i -> fourStarItems.add(Integer.toString(i))); + Arrays.stream(banner.getFallbackItems4Pool2()).forEach(i -> fourStarItems.add(Integer.toString(i))); response = response.replace("{{FOUR_STARS}}", "[" + String.join(",", fourStarItems) + "]"); // Add 3-star items. Set threeStarItems = new LinkedHashSet<>(); - Arrays.stream(manager.getBlueWeapons()).forEach(i -> threeStarItems.add(Integer.toString(i))); + Arrays.stream(banner.getFallbackItems3()).forEach(i -> threeStarItems.add(Integer.toString(i))); response = response.replace("{{THREE_STARS}}", "[" + String.join(",", threeStarItems) + "]"); // Done. From d4eb686fe2f7bd1afd7ee8c74068b7e6fb565fab Mon Sep 17 00:00:00 2001 From: kyoko12 Date: Fri, 13 May 2022 14:10:02 +0200 Subject: [PATCH 43/59] Don't silently delete config.json if there is an error. --- .../java/emu/grasscutter/Grasscutter.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/main/java/emu/grasscutter/Grasscutter.java b/src/main/java/emu/grasscutter/Grasscutter.java index 2af2415ab..af8a759a5 100644 --- a/src/main/java/emu/grasscutter/Grasscutter.java +++ b/src/main/java/emu/grasscutter/Grasscutter.java @@ -153,16 +153,22 @@ public final class Grasscutter { * Attempts to load the configuration from a file. */ public static void loadConfig() { + // Check if config.json exists. If not, we generate a new config. + if (!configFile.exists()) { + getLogger().info("config.json could not be found. Generating a default configuration ..."); + config = new ConfigContainer(); + Grasscutter.saveConfig(config); + return; + } + + // If the file already exists, we attempt to load it. try (FileReader file = new FileReader(configFile)) { config = gson.fromJson(file, ConfigContainer.class); - } catch (Exception exception) { - Grasscutter.saveConfig(null); - config = new ConfigContainer(); - } catch (Error error) { - // Occurred probably from an outdated config file. - Grasscutter.saveConfig(null); - config = new ConfigContainer(); - } + } + catch (Exception exception) { + getLogger().error("There was an error while trying to load the configuration from config.json. Please make sure that there are no syntax errors. If you want to start with a default configuration, delete your existing config.json."); + System.exit(1); + } } public static void loadLanguage() { From 891c70e5ea8689c8c0e799bc3a50a7b31cb99940 Mon Sep 17 00:00:00 2001 From: Kimi <34180607+Kimi898246@users.noreply.github.com> Date: Fri, 13 May 2022 20:10:40 +0800 Subject: [PATCH 44/59] Traditional Chinese | Translation Patches --- src/main/resources/languages/zh-TW.json | 146 ++++++++++++++++-------- 1 file changed, 98 insertions(+), 48 deletions(-) diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index f6ae52c9d..2b6fe34ff 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -16,6 +16,9 @@ "no_keystore_error": "[Dispatch] 未找到 SSL 憑證!已後降到 HTTP 伺服器。", "default_password": "[Dispatch] 默認的 keystore 密碼加載成功。請考慮將 config.json 的憑證密碼設定成 123456。" }, + "authentication": { + "default_unable_to_verify": "[驗證系統] 稱為 verifyUser 方法的東西在默認身份驗證程序中不可用。" + }, "no_commands_error": "此指令不適用於Dispatch-only模式。", "unhandled_request_error": "[Dispatch] 潛在的未處理請求 %s 請求:%s", "account": { @@ -46,7 +49,7 @@ "run_mode_help": "伺服器運行模式必須為 HYBRID 或者 DISPATCH_ONLY 或者 GAME_ONLY。Grasscutter 啟動失敗...", "create_resources": "正在建立 resources 資料夾...", "resources_error": "請將 BinOutput 和 ExcelBinOutput 複製到 resources 資料夾。", - "version": "Grasscutter版本: %s-%s" + "version": "Grasscutter版本: %s-%s" } }, "commands": { @@ -57,6 +60,7 @@ "console_execute_error": "此指令只能在伺服器的命令提示字元執行。", "player_execute_error": "請在遊戲裡使用這條指令。", "command_exist_error": "找不到指令。", + "no_description_specified": "没有指定說明。", "invalid": { "amount": "無效的數量。", "artifactId": "無效的聖遺物ID。", @@ -96,17 +100,20 @@ "create": "已建立帳號,UID 為 %s 。", "delete": "帳號已刪除。", "no_account": "帳號不存在。", - "command_usage": "用法:account [uid]" + "command_usage": "用法:account [uid]", + "description": "建立或刪除帳號。" }, "broadcast": { "command_usage": "用法:broadcast ", - "message_sent": "公告已發送。" + "message_sent": "公告已發送。", + "description": "向所有玩家發送公告。" }, "changescene": { "usage": "用法:changescene ", "already_in_scene": "你已經在這個場景中了。", "success": "已切換至場景 %s.", - "exists_error": "此場景不存在。" + "exists_error": "此場景不存在。", + "description": "切換指定場景。" }, "clear": { "command_usage": "用法: clear ", @@ -116,35 +123,41 @@ "furniture": "已將 %s 的塵歌壺家具清空。", "displays": "已清除 %s 的顯示。", "virtuals": "已將 %s 的所有貨幣和經驗值清空。", - "everything": "已將 %s 的所有物品清空。" + "everything": "已將 %s 的所有物品清空。", + "description": "從你的背包中刪除所有未裝備且未上鎖的物品,包括稀有物品。" }, "coop": { "usage": "用法:coop ", - "success": "召喚了 %s 到 %s 的世界。" + "success": "召喚了 %s 到 %s 的世界。", + "description": "強制傳送指定用戶到他人的世界。" }, "enter_dungeon": { "usage": "用法:enterdungeon ", - "changed": "已進入副本 %s", - "not_found_error": "此副本不存在。", - "in_dungeon_error": "你已經在祕境中了。" + "changed": "已進入祕境 %s", + "not_found_error": "此祕境不存在。", + "in_dungeon_error": "你已經在祕境中了。", + "description": "進入指定祕境。" }, "giveAll": { "usage": "用法:giveall [player] [amount]", "started": "正在賦予全部物品...", "success": "已賦予全部物品。", - "invalid_amount_or_playerId": "無效的數量/玩家ID。" + "invalid_amount_or_playerId": "無效的數量/玩家ID。", + "description": "賦予所有物品。" }, "giveArtifact": { "usage": "用法:giveart|gart [player] [[,]]... [level]", "id_error": "無效的聖遺物ID。", - "success": "已把 %s 給予 %s。" + "success": "已把 %s 給予 %s。", + "description": "給予指定聖遺物。" }, "giveChar": { "usage": "用法:givechar [amount]", "given": "已將 %s 等級 %s 給予 %s。", "invalid_avatar_id": "無效的角色ID。", "invalid_avatar_level": "無效的角色等級。.", - "invalid_avatar_or_player_id": "無效的角色ID/玩家ID。" + "invalid_avatar_or_player_id": "無效的角色ID/玩家ID。", + "description": "給予指定角色。" }, "give": { "usage": "用法:give [amount] [level]", @@ -152,29 +165,42 @@ "refinement_must_between_1_and_5": "精煉度必需在 1 到 5 之間。", "given": "已經將 %s 個 %s 給予 %s。", "given_with_level_and_refinement": "已將 %s [等級%s, 精煉%s] %s個給予 %s", - "given_level": "已將 %s 等級 %s %s 個給予 %s" + "given_level": "已將 %s 等級 %s %s 個給予 %s", + "description": "給予指定物品。" }, "godmode": { - "success": "上帝模式設定為 %s 。 [用戶:%s]" + "success": "上帝模式設定為 %s 。 [用戶:%s]", + "description": "防止你受到傷害。" }, "heal": { - "success": "所有角色已被治療。" + "success": "所有角色已被治療。", + "description": "治療當前隊伍的角色。" }, "kick": { "player_kick_player": "玩家 [%s:%s] 已把 [%s:%s] 踢出", - "server_kick_player": "正在踢出玩家 [%s:%s]" + "server_kick_player": "正在踢出玩家 [%s:%s]", + "description": "從伺服器內踢出指定玩家。" }, "kill": { "usage": "用法:killall [playerUid] [sceneId]", "scene_not_found_in_player_world": "未在玩家世界中找到此場景", - "kill_monsters_in_scene": "已殺死 %s 個怪物。 [場景ID: %s]" + "kill_monsters_in_scene": "已殺死 %s 個怪物。 [場景ID: %s]", + "description": "殺死所有怪物。" }, "killCharacter": { "usage": "用法:/killcharacter [playerId]", - "success": "已殺死 %s 目前的場上角色。" + "success": "已殺死 %s 目前的場上角色。", + "description": "殺死玩家目前使用的場上角色。" + }, + "language": { + "current_language": "當前語言是: %s", + "language_changed": "語言切換至: %s", + "language_not_found": "目前客戶端沒有這種語言: %s", + "description": "顯示或切換當前語言。" }, "list": { - "success": "目前總線上人數:%s" + "success": "目前總線上人數:%s" , + "description": "查看所有在線玩家" }, "permission": { "usage": "用法:permission ", @@ -182,21 +208,26 @@ "has_error": "此玩家已擁有權限!", "remove": "權限已移除。", "not_have_error": "此玩家未擁有權限!", - "account_error": "The account cannot be found." + "account_error": "帳號不存在。", + "description": "指派或移除指定玩家的權限。" }, "position": { - "success": "坐標:%s, %s, %s\n場景ID:%s" + "success": "座標:%s, %s, %s\n場景ID:%s", + "description": "獲取目前所在位置的座標。" }, "reload": { "reload_start": "正在重新加載設定檔。", - "reload_done": "重新加載已完成。" + "reload_done": "重新加載已完成。", + "description": "重新加載設定檔和數據。" }, "resetConst": { "reset_all": "重設所有角色的命座。", - "success": "已重設 %s 的命座,重新登入後將會生效。" + "success": "已重設 %s 的命座,重新登入後將會生效。", + "description": "重置當前角色的命之座,重新登入後將會生效。" }, "resetShopLimit": { - "usage": "用法:/resetshop " + "usage": "用法:/resetshop ", + "description": "重置所選玩家的商店刷新時間。" }, "sendMail": { "usage": "用法:give [player] [amount]", @@ -218,17 +249,20 @@ "message": "<正文>", "sender": "<寄件者>", "arguments": " [數量] [等級]", - "error": "錯誤:無效的編寫階段 %s。需要 stacktrace 請查看伺服器命令提示字元。" + "error": "錯誤:無效的編寫階段 %s。需要 stacktrace 請查看伺服器命令提示字元。", + "description": "向指定用戶發送郵件。此指令的用法可根據附加的參數而改變。" }, "sendMessage": { "usage": "用法:sendmessage ", - "success": "訊息已發送。" + "success": "訊息已發送。", + "description": "向指定玩家發送訊息。" }, "setFetterLevel": { "usage": "用法:setfetterlevel ", "range_error": "好感度必須在 0 到 10 之間。", "success": "好感等級已設定為 %s", - "level_error": "無效的好感度。" + "level_error": "無效的好感度。", + "description": "設定當前角色的好感度等級。" }, "setStats": { "usage_console": "用法:setstats|stats @ ", @@ -239,77 +273,93 @@ "player_error": "玩家不存在或已離線。", "set_self": "%s 已經設為 %s。", "set_for_uid": "%s 的使用者 %s 更改為 %s。", - "set_max_hp": "最大生命值更改為 %s。" + "set_max_hp": "最大生命值更改為 %s。", + "description": "設定當前角色的數據類型。" }, "setWorldLevel": { "usage": "用法:setworldlevel ", "value_error": "世界等級必須設定在0-8之間。", "success": "已將世界等級設為%s。", - "invalid_world_level": "無效的世界等級。" + "invalid_world_level": "無效的世界等級。", + "description": "設定世界等級,執行指令後需重新登入後才會生效。" }, "spawn": { "usage": "用法:spawn [amount] [level(僅限怪物)]", - "success": "已生成 %s 個 %s。" + "success": "已生成 %s 個 %s。", + "description": "在你附近生成一個實體動物。" }, "stop": { - "success": "正在關閉伺服器..." + "success": "正在關閉伺服器...", + "description": "以正常的方式關閉伺服器。" }, "talent": { "usage_1": "設定天賦等級:/talent set ", "usage_2": "另一種設定天賦等級的指令使用方法:/talent ", "usage_3": "獲取天賦ID指令用法:/talent getid", - "lower_16": "無效的技能等級,技能等級應低於 16。", + "lower_16": "無效的天賦等級,技能等級應低於 16。", "set_id": "將天賦等級設為%s。", "set_atk": "將普通攻擊等級設為 %s。", - "set_e": "設定天賦E等級至 %s。", - "set_q": "設定天賦Q等級至 %s。", + "set_e": "設定元素戰技的天賦等級至 %s。", + "set_q": "設定元素爆發的天賦等級至 %s。", "invalid_skill_id": "無效的技能ID。", "set_this": "將天賦等級設為 %s。", "invalid_level": "無效的天賦等級。", "normal_attack_id": "普通攻擊的 ID 為 %s。", - "e_skill_id": "E技能ID %s。", - "q_skill_id": "Q技能ID %s。" + "e_skill_id": "元素戰技技能ID %s。", + "q_skill_id": "元素爆發技能ID %s。", + "description": "設定當前角色的天賦等級" }, "teleportAll": { "success": "召喚了所有玩家到你的位置上。", - "error": "此指令僅可在多人遊戲下可用。" + "error": "此指令僅可在多人遊戲下可用。", + "description": "將你世界裡的所有玩家傳送到你目前的所在位置。" }, "teleport": { "usage_server": "用法:/tp @ [scene id]", "usage": "用法:/tp [@] [scene id]", "specify_player_id": "你必須指定一個玩家ID。", - "invalid_position": "無效的位置。", - "success": "傳送 %s 到坐標 %s,%s,%s ,場景為 %s" + "invalid_position": "無效的座標。", + "success": "傳送 %s 到座標 %s,%s,%s ,場景為 %s 。", + "description": "將玩家的位置傳送到你所指定的座標。" + }, + "tower": { + "unlock_done": "解鎖所有級別的深境螺旋已全部解鎖。" }, "weather": { "usage": "用法:weather [climateId]", "success": "已將當前天氣設定為 %s ,氣候則為 %s 。", - "invalid_id": "無效的ID。" + "invalid_id": "無效的ID。", + "description": "更改目前的天氣。" }, "drop": { "command_usage": "用法:drop [amount]", - "success": "已將 %s x %s 丟在附近。" + "success": "已將 %s x %s 丟在附近。", + "description": "在你附近丟下一個物品。" }, "help": { "usage": "用法:", "aliases": "別名:", + "description": "發送幫助信息或顯示特定命令的信息", "available_commands": "可用指令:" }, + "restart": { + "description": "重新啟動伺服器。" + }, "unlocktower": { "success": "解鎖完成。", - "description": "解鎖所有級別的深境螺旋" + "description": "解鎖所有級別的深境螺旋。" }, "resetshop": { - "description": "重置商店時間" + "description": "重置商店刷新時間。" } }, "gacha": { "details": { - "title": "Banner Details", - "available_five_stars": "Available 5-star Items", - "available_four_stars": "Available 4-star Items", - "available_three_stars": "Available 3-star Items", - "template_missing": "data/gacha_details.html is missing." + "title": "祈願詳情", + "available_five_stars": "可獲得的5星物品", + "available_four_stars": "可獲得的4星物品", + "available_three_stars": "可獲得的3星物品", + "template_missing": "data/gacha_details.html 不存在。" } } } From a227b44c70244a6b1574881e6110e491c3a2b27c Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 13 May 2022 06:24:50 -0700 Subject: [PATCH 45/59] Fixed quests not finishing their questline --- .../java/emu/grasscutter/data/def/QuestData.java | 11 +++++++++++ .../emu/grasscutter/game/quest/GameMainQuest.java | 1 + .../java/emu/grasscutter/game/quest/GameQuest.java | 13 ++++++++++--- .../emu/grasscutter/game/quest/QuestManager.java | 2 +- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/main/java/emu/grasscutter/data/def/QuestData.java b/src/main/java/emu/grasscutter/data/def/QuestData.java index 31ac2ce7e..13e806dab 100644 --- a/src/main/java/emu/grasscutter/data/def/QuestData.java +++ b/src/main/java/emu/grasscutter/data/def/QuestData.java @@ -15,6 +15,9 @@ public class QuestData extends GameResource { private int Order; private long DescTextMapHash; + private boolean FinishParent; + private boolean IsRewind; + private LogicType AcceptCondComb; private QuestCondition[] acceptConditons; private LogicType FinishCondComb; @@ -45,6 +48,14 @@ public class QuestData extends GameResource { return DescTextMapHash; } + public boolean finishParent() { + return FinishParent; + } + + public boolean isRewind() { + return IsRewind; + } + public LogicType getAcceptCondComb() { return AcceptCondComb; } diff --git a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java index bf88b8efe..613819d0b 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java @@ -91,6 +91,7 @@ public class GameMainQuest { this.isFinished = true; this.state = ParentQuestState.PARENT_QUEST_STATE_FINISHED; this.getOwner().getSession().send(new PacketFinishedParentQuestUpdateNotify(this)); + this.save(); } public void save() { diff --git a/src/main/java/emu/grasscutter/game/quest/GameQuest.java b/src/main/java/emu/grasscutter/game/quest/GameQuest.java index b242166eb..5e1126fcb 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameQuest.java @@ -143,21 +143,28 @@ public class GameQuest { this.getOwner().getSession().send(new PacketQuestProgressUpdateNotify(this)); this.getOwner().getSession().send(new PacketQuestListUpdateNotify(this)); - this.save(); - this.tryAcceptQuestLine(); + if (this.getData().finishParent()) { + // This quest finishes the questline - the main quest will also save the quest to db so we dont have to call save() here + this.getMainQuest().finish(); + } else { + // Try and accept other quests if possible + this.tryAcceptQuestLine(); + this.save(); + } } public boolean tryAcceptQuestLine() { try { MainQuestData questConfig = GameData.getMainQuestDataMap().get(this.getMainQuestId()); + for (SubQuestData subQuest : questConfig.getSubQuests()) { GameQuest quest = getMainQuest().getChildQuestById(subQuest.getSubId()); if (quest == null) { QuestData questData = GameData.getQuestDataMap().get(subQuest.getSubId()); - if (questData == null) { + if (questData == null || questData.getAcceptCond() == null) { continue; } diff --git a/src/main/java/emu/grasscutter/game/quest/QuestManager.java b/src/main/java/emu/grasscutter/game/quest/QuestManager.java index 548e8241a..745ce9ef8 100644 --- a/src/main/java/emu/grasscutter/game/quest/QuestManager.java +++ b/src/main/java/emu/grasscutter/game/quest/QuestManager.java @@ -128,7 +128,7 @@ public class QuestManager { QuestData data = quest.getData(); for (int i = 0; i < data.getFinishCond().length; i++) { - if (quest.getFinishProgressList()[i] == 1) { + if (quest.getFinishProgressList() == null || quest.getFinishProgressList()[i] == 1) { continue; } From 83f8bec9470b163f01ced39dbf6b3dd14fe8e0ca Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 13 May 2022 06:33:12 -0700 Subject: [PATCH 46/59] Implement QUEST_CONTENT_FINISH_PLOT --- .../game/quest/content/ContentFinishPlot.java | 17 +++++++++++++++++ .../server/packet/recv/HandlerNpcTalkReq.java | 2 ++ 2 files changed, 19 insertions(+) create mode 100644 src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java b/src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java new file mode 100644 index 000000000..d8e0cd4e5 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java @@ -0,0 +1,17 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.data.def.QuestData.QuestCondition; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_CONTENT_FINISH_PLOT) +public class ContentFinishPlot extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestCondition condition, int... params) { + return condition.getParam()[0] == params[0]; + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java index 82248c98c..3dae7fe10 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerNpcTalkReq.java @@ -16,7 +16,9 @@ public class HandlerNpcTalkReq extends PacketHandler { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { NpcTalkReq req = NpcTalkReq.parseFrom(payload); + // Why are there 2 quest triggers that do the same thing... session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_COMPLETE_TALK, req.getTalkId()); + session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_FINISH_PLOT, req.getTalkId()); session.send(new PacketNpcTalkRsp(req.getNpcEntityId(), req.getTalkId(), req.getEntityId())); } From 19ee983c086042042f17843211b5cc7abc765bb9 Mon Sep 17 00:00:00 2001 From: Yazawazi <47273265+Yazawazi@users.noreply.github.com> Date: Sat, 14 May 2022 00:03:55 +0800 Subject: [PATCH 47/59] feature(serenitea pot): Implementation of the entry function It's being perfected, so don't worry. (probably) --- .../packet/recv/HandlerWidgetDoBagReq.java | 57 +++++++++++++++++++ .../send/PacketWidgetCoolDownNotify.java | 25 ++++++++ .../packet/send/PacketWidgetDoBagRsp.java | 28 +++++++++ .../send/PacketWidgetGadgetDataNotify.java | 26 +++++++++ 4 files changed, 136 insertions(+) create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerWidgetDoBagReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketWidgetCoolDownNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketWidgetDoBagRsp.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketWidgetGadgetDataNotify.java diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerWidgetDoBagReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWidgetDoBagReq.java new file mode 100644 index 000000000..d9cee08de --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerWidgetDoBagReq.java @@ -0,0 +1,57 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.def.GadgetData; +import emu.grasscutter.game.entity.EntityVehicle; +import emu.grasscutter.game.entity.GameEntity; +import emu.grasscutter.game.props.LifeState; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.*; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketSceneEntityAppearNotify; +import emu.grasscutter.server.packet.send.PacketWidgetCoolDownNotify; +import emu.grasscutter.server.packet.send.PacketWidgetDoBagRsp; +import emu.grasscutter.server.packet.send.PacketWidgetGadgetDataNotify; +import emu.grasscutter.utils.Position; + +import java.util.List; + +@Opcodes(PacketOpcodes.WidgetDoBagReq) +public class HandlerWidgetDoBagReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + WidgetDoBagReqOuterClass.WidgetDoBagReq req = WidgetDoBagReqOuterClass.WidgetDoBagReq.parseFrom(payload); + switch (req.getMaterialId()) { + case 220026 -> { + GadgetData gadgetData = GameData.getGadgetDataMap().get(70500025); + Position pos = new Position(req.getWidgetCreatorInfo().getLocationInfo().getPos()); + Position rot = new Position(req.getWidgetCreatorInfo().getLocationInfo().getRot()); + GameEntity entity = new EntityVehicle( + session.getPlayer().getScene(), + session.getPlayer(), + gadgetData.getId(), + 0, + pos, + rot + ); + + session.getPlayer().getScene().addEntity(entity); + + session.send(new PacketWidgetGadgetDataNotify(70500025, List.of(entity.getId()))); // ??? + session.send(new PacketWidgetCoolDownNotify(15, System.currentTimeMillis() + 5000L, true)); + session.send(new PacketWidgetCoolDownNotify(15, System.currentTimeMillis() + 5000L, true)); + // Send twice, and I don't know why, Ask mhy + session.send(new PacketWidgetDoBagRsp()); + } + default -> { + session.send(new PacketWidgetDoBagRsp()); + } + + } + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetCoolDownNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetCoolDownNotify.java new file mode 100644 index 000000000..a73187020 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetCoolDownNotify.java @@ -0,0 +1,25 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WidgetCoolDownDataOuterClass; +import emu.grasscutter.net.proto.WidgetCoolDownNotifyOuterClass; + +public class PacketWidgetCoolDownNotify extends BasePacket { + + public PacketWidgetCoolDownNotify(int id, long coolDownTime, boolean isSuccess) { + super(PacketOpcodes.WidgetCoolDownNotify); + + WidgetCoolDownNotifyOuterClass.WidgetCoolDownNotify proto = WidgetCoolDownNotifyOuterClass.WidgetCoolDownNotify.newBuilder() + .addGroupCoolDownDataList( + WidgetCoolDownDataOuterClass.WidgetCoolDownData.newBuilder() + .setId(id) + .setCoolDownTime(coolDownTime) + .setIsSuccess(isSuccess) + .build() + ) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetDoBagRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetDoBagRsp.java new file mode 100644 index 000000000..7ce5065ea --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetDoBagRsp.java @@ -0,0 +1,28 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WidgetDoBagRspOuterClass; + +public class PacketWidgetDoBagRsp extends BasePacket { + + public PacketWidgetDoBagRsp(int materialId) { + super(PacketOpcodes.WidgetDoBagRsp); + + WidgetDoBagRspOuterClass.WidgetDoBagRsp proto = WidgetDoBagRspOuterClass.WidgetDoBagRsp.newBuilder() + .setMaterialId(materialId) + .setRetcode(0) + .build(); + + this.setData(proto); + } + + public PacketWidgetDoBagRsp() { + super(PacketOpcodes.WidgetDoBagRsp); + + WidgetDoBagRspOuterClass.WidgetDoBagRsp proto = WidgetDoBagRspOuterClass.WidgetDoBagRsp.newBuilder() + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetGadgetDataNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetGadgetDataNotify.java new file mode 100644 index 000000000..f94c6c10e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketWidgetGadgetDataNotify.java @@ -0,0 +1,26 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.WidgetGadgetDataNotifyOuterClass; +import emu.grasscutter.net.proto.WidgetGadgetDataOuterClass; + +import java.io.IOException; +import java.util.List; + +public class PacketWidgetGadgetDataNotify extends BasePacket { + public PacketWidgetGadgetDataNotify(int gadgetId, List gadgetEntityIdList) throws IOException { + super(PacketOpcodes.WidgetGadgetDataNotify); + + WidgetGadgetDataNotifyOuterClass.WidgetGadgetDataNotify proto = WidgetGadgetDataNotifyOuterClass.WidgetGadgetDataNotify.newBuilder() + .setWidgetGadgetData( + WidgetGadgetDataOuterClass.WidgetGadgetData.newBuilder() + .setGadgetId(gadgetId) + .addAllGadgetEntityIdList(gadgetEntityIdList) + .build() + ) + .build(); + + this.setData(proto); + } +} From 95a062123f63edfda7a48200e431712e9a1ca74c Mon Sep 17 00:00:00 2001 From: Yazawazi <47273265+Yazawazi@users.noreply.github.com> Date: Sat, 14 May 2022 03:20:20 +0800 Subject: [PATCH 48/59] feature(serenitea pot): Implementation of enter Ugly hard code --- .../emu/grasscutter/game/player/Player.java | 30 ++++++++ .../recv/HandlerHomeChooseModuleReq.java | 26 +++++++ .../packet/recv/HandlerTryEnterHomeReq.java | 68 +++++++++++++++++++ .../send/PacketHomeChooseModuleRsp.java | 19 ++++++ .../send/PacketHomeComfortInfoNotify.java | 40 +++++++++++ .../send/PacketPlayerHomeCompInfoNotify.java | 32 +++++++++ .../packet/send/PacketTryEnterHomeRsp.java | 30 ++++++++ 7 files changed, 245 insertions(+) create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerHomeChooseModuleReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketHomeChooseModuleRsp.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketHomeComfortInfoNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketPlayerHomeCompInfoNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketTryEnterHomeRsp.java diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index b7d8470bf..9c9f1ea56 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -87,6 +87,9 @@ public class Player { private Integer widgetId; + private Set realmList; + private Integer currentRealmId; + @Transient private long nextGuid = 0; @Transient private int peerId; @Transient private World world; @@ -313,6 +316,31 @@ public class Player { this.widgetId = widgetId; } + public Set getRealmList() { + return realmList; + } + + public void setRealmList(Set realmList) { + this.realmList = realmList; + } + + public void addRealmList(int realmId) { + if (this.realmList == null) { + this.realmList = new HashSet<>(); + } else if (this.realmList.contains(realmId)) { + return; + } + this.realmList.add(realmId); + } + + public Integer getCurrentRealmId() { + return currentRealmId; + } + + public void setCurrentRealmId(Integer currentRealmId) { + this.currentRealmId = currentRealmId; + } + public Position getPos() { return pos; } @@ -1187,6 +1215,8 @@ public class Player { session.send(new PacketServerCondMeetQuestListUpdateNotify(this)); session.send(new PacketAllWidgetDataNotify(this)); session.send(new PacketWidgetGadgetAllDataNotify()); + session.send(new PacketPlayerHomeCompInfoNotify(this)); + session.send(new PacketHomeComfortInfoNotify(this)); getTodayMoonCard(); // The timer works at 0:0, some users log in after that, use this method to check if they have received a reward today or not. If not, send the reward. diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerHomeChooseModuleReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerHomeChooseModuleReq.java new file mode 100644 index 000000000..5a7c0dbe5 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerHomeChooseModuleReq.java @@ -0,0 +1,26 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.HomeChooseModuleReqOuterClass; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketHomeChooseModuleRsp; +import emu.grasscutter.server.packet.send.PacketHomeComfortInfoNotify; +import emu.grasscutter.server.packet.send.PacketPlayerHomeCompInfoNotify; + + +@Opcodes(PacketOpcodes.HomeChooseModuleReq) +public class HandlerHomeChooseModuleReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + HomeChooseModuleReqOuterClass.HomeChooseModuleReq req = + HomeChooseModuleReqOuterClass.HomeChooseModuleReq.parseFrom(payload); + session.getPlayer().addRealmList(req.getModuleId()); + session.getPlayer().setCurrentRealmId(req.getModuleId()); + session.send(new PacketHomeChooseModuleRsp(req.getModuleId())); + session.send(new PacketPlayerHomeCompInfoNotify(session.getPlayer())); + session.send(new PacketHomeComfortInfoNotify(session.getPlayer())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java new file mode 100644 index 000000000..3e78bcb3a --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java @@ -0,0 +1,68 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.TryEnterHomeReqOuterClass; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketTryEnterHomeRsp; +import emu.grasscutter.utils.Position; + +@Opcodes(PacketOpcodes.TryEnterHomeReq) +public class HandlerTryEnterHomeReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + TryEnterHomeReqOuterClass.TryEnterHomeReq req = + TryEnterHomeReqOuterClass.TryEnterHomeReq.parseFrom(payload); + + if (req.getTargetUid() != session.getPlayer().getUid()) { + // I hope that tomorrow there will be a hero who can support multiplayer mode and write code like a poem + session.send(new PacketTryEnterHomeRsp()); + return; + } + + // Hardcoded for now + switch (session.getPlayer().getCurrentRealmId()) { + case 1: + session.getPlayer().getWorld().transferPlayerToScene( + session.getPlayer(), + 2001, + new Position(839, 319, 137) + ); + break; + + case 2: + session.getPlayer().getWorld().transferPlayerToScene( + session.getPlayer(), + 2002, + new Position(605, 444, 554) + ); + break; + + case 3: + session.getPlayer().getWorld().transferPlayerToScene( + session.getPlayer(), + 2003, + new Position(511, 229, 605) + ); + break; + + case 4: + session.getPlayer().getWorld().transferPlayerToScene( + session.getPlayer(), + 2004, + new Position(239, 187, 536) + ); + break; + + default: + session.send(new PacketTryEnterHomeRsp()); + return; + } + + + session.send(new PacketTryEnterHomeRsp(req.getTargetUid())); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketHomeChooseModuleRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketHomeChooseModuleRsp.java new file mode 100644 index 000000000..e7b3ff1ea --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketHomeChooseModuleRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.HomeChooseModuleRspOuterClass; + +public class PacketHomeChooseModuleRsp extends BasePacket { + + public PacketHomeChooseModuleRsp(int moduleId) { + super(PacketOpcodes.HomeChooseModuleRsp); + + HomeChooseModuleRspOuterClass.HomeChooseModuleRsp proto = HomeChooseModuleRspOuterClass.HomeChooseModuleRsp.newBuilder() + .setRetcode(0) + .setModuleId(moduleId) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketHomeComfortInfoNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketHomeComfortInfoNotify.java new file mode 100644 index 000000000..47e46dfdb --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketHomeComfortInfoNotify.java @@ -0,0 +1,40 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.HomeComfortInfoNotifyOuterClass; +import emu.grasscutter.net.proto.HomeModuleComfortInfoOuterClass; + +import java.util.ArrayList; +import java.util.List; + +public class PacketHomeComfortInfoNotify extends BasePacket { + + public PacketHomeComfortInfoNotify(Player player) { + super(PacketOpcodes.HomeComfortInfoNotify); + + if (player.getRealmList() == null) { + // Do not send + return; + } + + List comfortInfoList = new ArrayList<>(); + + for (int moduleId : player.getRealmList()) { + comfortInfoList.add( + HomeModuleComfortInfoOuterClass.HomeModuleComfortInfo.newBuilder() + .setModuleId(moduleId) + .build() + ); + } + + HomeComfortInfoNotifyOuterClass.HomeComfortInfoNotify proto = HomeComfortInfoNotifyOuterClass.HomeComfortInfoNotify + .newBuilder() + .addAllModuleInfoList(comfortInfoList) + .build(); + + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerHomeCompInfoNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerHomeCompInfoNotify.java new file mode 100644 index 000000000..29a6964b5 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPlayerHomeCompInfoNotify.java @@ -0,0 +1,32 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PlayerHomeCompInfoNotifyOuterClass; +import emu.grasscutter.net.proto.PlayerHomeCompInfoOuterClass; + +import java.util.List; + +public class PacketPlayerHomeCompInfoNotify extends BasePacket { + + public PacketPlayerHomeCompInfoNotify(Player player) { + super(PacketOpcodes.PlayerHomeCompInfoNotify); + + if (player.getRealmList() == null) { + // Do not send + return; + } + + PlayerHomeCompInfoNotifyOuterClass.PlayerHomeCompInfoNotify proto = PlayerHomeCompInfoNotifyOuterClass.PlayerHomeCompInfoNotify.newBuilder() + .setCompInfo( + PlayerHomeCompInfoOuterClass.PlayerHomeCompInfo.newBuilder() + .addAllUnlockedModuleIdList(player.getRealmList()) + .addAllLevelupRewardGotLevelList(List.of(1)) // Hardcoded + .build() + ) + .build(); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTryEnterHomeRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTryEnterHomeRsp.java new file mode 100644 index 000000000..369c44140 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTryEnterHomeRsp.java @@ -0,0 +1,30 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.RetcodeOuterClass; +import emu.grasscutter.net.proto.TryEnterHomeRspOuterClass; + +public class PacketTryEnterHomeRsp extends BasePacket { + + public PacketTryEnterHomeRsp() { + super(PacketOpcodes.TryEnterHomeRsp); + + TryEnterHomeRspOuterClass.TryEnterHomeRsp proto = TryEnterHomeRspOuterClass.TryEnterHomeRsp.newBuilder() + .setRetcode(RetcodeOuterClass.Retcode.RET_SVR_ERROR_VALUE) + .build(); + + this.setData(proto); + } + + public PacketTryEnterHomeRsp(int uid) { + super(PacketOpcodes.TryEnterHomeRsp); + + TryEnterHomeRspOuterClass.TryEnterHomeRsp proto = TryEnterHomeRspOuterClass.TryEnterHomeRsp.newBuilder() + .setRetcode(0) + .setTargetUid(uid) + .build(); + + this.setData(proto); + } +} From ab6aa96144d858a88c8ea35fc13b41c031b024d0 Mon Sep 17 00:00:00 2001 From: Yazawazi <47273265+Yazawazi@users.noreply.github.com> Date: Sat, 14 May 2022 04:50:31 +0800 Subject: [PATCH 49/59] fix(serenitea pot): teleport & read born pos from lua --- .../emu/grasscutter/game/world/World.java | 5 ++ .../packet/recv/HandlerTryEnterHomeReq.java | 48 +++++-------------- 2 files changed, 16 insertions(+), 37 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/world/World.java b/src/main/java/emu/grasscutter/game/world/World.java index 95356a15a..22048077d 100644 --- a/src/main/java/emu/grasscutter/game/world/World.java +++ b/src/main/java/emu/grasscutter/game/world/World.java @@ -268,6 +268,11 @@ public class World implements Iterable { } else if (oldScene == newScene) { enterType = EnterType.ENTER_GOTO; } + + // Home + if (2001 <= newScene.getId() && newScene.getId() <= 2004) { + enterType = EnterType.ENTER_SELF_HOME; + } // Teleport packet player.sendPacket(new PacketPlayerEnterSceneNotify(player, enterType, enterReason, sceneId, pos)); diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java index 3e78bcb3a..5df106df2 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerTryEnterHomeReq.java @@ -1,10 +1,12 @@ package emu.grasscutter.server.packet.recv; -import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.world.Scene; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.TryEnterHomeReqOuterClass; +import emu.grasscutter.scripts.data.SceneConfig; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.PacketTryEnterHomeRsp; import emu.grasscutter.utils.Position; @@ -23,44 +25,16 @@ public class HandlerTryEnterHomeReq extends PacketHandler { return; } - // Hardcoded for now - switch (session.getPlayer().getCurrentRealmId()) { - case 1: - session.getPlayer().getWorld().transferPlayerToScene( - session.getPlayer(), - 2001, - new Position(839, 319, 137) - ); - break; + int realmId = 2000 + session.getPlayer().getCurrentRealmId(); - case 2: - session.getPlayer().getWorld().transferPlayerToScene( - session.getPlayer(), - 2002, - new Position(605, 444, 554) - ); - break; + Scene scene = session.getPlayer().getWorld().getSceneById(realmId); + Position pos = scene.getScriptManager().getConfig().born_pos; - case 3: - session.getPlayer().getWorld().transferPlayerToScene( - session.getPlayer(), - 2003, - new Position(511, 229, 605) - ); - break; - - case 4: - session.getPlayer().getWorld().transferPlayerToScene( - session.getPlayer(), - 2004, - new Position(239, 187, 536) - ); - break; - - default: - session.send(new PacketTryEnterHomeRsp()); - return; - } + session.getPlayer().getWorld().transferPlayerToScene( + session.getPlayer(), + realmId, + pos + ); session.send(new PacketTryEnterHomeRsp(req.getTargetUid())); From 749ef3ff234e05a432357f956fc9f3fcf9b33e60 Mon Sep 17 00:00:00 2001 From: ShigemoriHakura <62388797+ShigemoriHakura@users.noreply.github.com> Date: Sat, 14 May 2022 07:33:07 +0800 Subject: [PATCH 50/59] Add support for codexQuests (#870) --- .../java/emu/grasscutter/data/GameData.java | 7 ++- .../emu/grasscutter/data/def/CodexQuest.java | 42 +++++++++++++++ .../emu/grasscutter/game/player/Player.java | 1 + .../grasscutter/game/quest/GameMainQuest.java | 2 + .../emu/grasscutter/game/quest/GameQuest.java | 1 + .../grasscutter/game/quest/QuestManager.java | 6 +++ .../send/PacketCodexDataFullNotify.java | 54 +++++++++++++++++++ .../send/PacketCodexDataUpdateNotify.java | 27 ++++++++++ 8 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 src/main/java/emu/grasscutter/data/def/CodexQuest.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataFullNotify.java create mode 100644 src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataUpdateNotify.java diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index 75b840202..ed5c469bb 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -9,7 +9,6 @@ import java.util.Map; import emu.grasscutter.Grasscutter; import emu.grasscutter.utils.Utils; import emu.grasscutter.data.custom.AbilityEmbryoEntry; -import emu.grasscutter.data.custom.AbilityModifier; import emu.grasscutter.data.custom.AbilityModifierEntry; import emu.grasscutter.data.custom.OpenConfigEntry; import emu.grasscutter.data.custom.MainQuestData; @@ -65,6 +64,8 @@ public class GameData { private static final Int2ObjectMap sceneDataMap = new Int2ObjectLinkedOpenHashMap<>(); private static final Int2ObjectMap fetterDataMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap codexQuestMap = new Int2ObjectOpenHashMap<>(); + private static final Int2ObjectMap codexQuestIdMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap rewardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap worldLevelDataMap = new Int2ObjectOpenHashMap<>(); @@ -293,6 +294,10 @@ public class GameData { return fetters; } + public static Int2ObjectMap getCodexQuestMap(){return codexQuestMap;} + + public static Int2ObjectMap getCodexQuestIdMap(){return codexQuestIdMap;} + public static Int2ObjectMap getWorldLevelDataMap() { return worldLevelDataMap; } diff --git a/src/main/java/emu/grasscutter/data/def/CodexQuest.java b/src/main/java/emu/grasscutter/data/def/CodexQuest.java new file mode 100644 index 000000000..578837e04 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/def/CodexQuest.java @@ -0,0 +1,42 @@ +package emu.grasscutter.data.def; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; + +@ResourceType(name = {"QuestCodexExcelConfigData.json"}, loadPriority = ResourceType.LoadPriority.HIGH) +public class CodexQuest extends GameResource { + private int Id; + private int ParentQuestId; + private int ChapterId; + private int SortOrder; + private boolean IsDisuse; + + public int getParentQuestId() { + return ParentQuestId; + } + + public int getId() { + return Id; + } + + public int getChapterId() { + return ChapterId; + } + + public int getSortOrder() { + return SortOrder; + } + + public boolean getIsDisuse() { + return IsDisuse; + } + + @Override + public void onLoad() { + if(!this.getIsDisuse()) { + GameData.getCodexQuestIdMap().put(this.getParentQuestId(), this); + } + } +} diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 9c9f1ea56..f86e09370 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -1212,6 +1212,7 @@ public class Player { session.send(new PacketAvatarDataNotify(this)); session.send(new PacketFinishedParentQuestNotify(this)); session.send(new PacketQuestListNotify(this)); + session.send(new PacketCodexDataFullNotify(this)); session.send(new PacketServerCondMeetQuestListUpdateNotify(this)); session.send(new PacketAllWidgetDataNotify(this)); session.send(new PacketWidgetGadgetAllDataNotify()); diff --git a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java index 613819d0b..c298913cc 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java @@ -3,6 +3,7 @@ package emu.grasscutter.game.quest; import java.util.HashMap; import java.util.Map; +import emu.grasscutter.server.packet.send.PacketCodexDataUpdateNotify; import org.bson.types.ObjectId; import dev.morphia.annotations.Entity; @@ -91,6 +92,7 @@ public class GameMainQuest { this.isFinished = true; this.state = ParentQuestState.PARENT_QUEST_STATE_FINISHED; this.getOwner().getSession().send(new PacketFinishedParentQuestUpdateNotify(this)); + this.getOwner().getSession().send(new PacketCodexDataUpdateNotify(this)); this.save(); } diff --git a/src/main/java/emu/grasscutter/game/quest/GameQuest.java b/src/main/java/emu/grasscutter/game/quest/GameQuest.java index 5e1126fcb..3caf950ba 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameQuest.java @@ -11,6 +11,7 @@ import emu.grasscutter.game.player.Player; import emu.grasscutter.game.quest.enums.LogicType; import emu.grasscutter.game.quest.enums.QuestState; import emu.grasscutter.net.proto.QuestOuterClass.Quest; +import emu.grasscutter.server.packet.send.PacketCodexDataUpdateNotify; import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify; import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify; import emu.grasscutter.utils.Utils; diff --git a/src/main/java/emu/grasscutter/game/quest/QuestManager.java b/src/main/java/emu/grasscutter/game/quest/QuestManager.java index 745ce9ef8..0d81834f0 100644 --- a/src/main/java/emu/grasscutter/game/quest/QuestManager.java +++ b/src/main/java/emu/grasscutter/game/quest/QuestManager.java @@ -66,6 +66,12 @@ public class QuestManager { } } } + + public void forEachMainQuest(Consumer callback) { + for (GameMainQuest mainQuest : getQuests().values()) { + callback.accept(mainQuest); + } + } // TODO public void forEachActiveQuest(Consumer callback) { diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataFullNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataFullNotify.java new file mode 100644 index 000000000..760c3b3d2 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataFullNotify.java @@ -0,0 +1,54 @@ +package emu.grasscutter.server.packet.send; + +import java.util.Collections; +import java.util.List; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CodexDataFullNotifyOuterClass.CodexDataFullNotify; +import emu.grasscutter.net.proto.CodexTypeDataOuterClass.CodexTypeData; +import emu.grasscutter.net.proto.CodexTypeOuterClass; +import emu.grasscutter.server.game.GameSession; + +public class PacketCodexDataFullNotify extends BasePacket { + public PacketCodexDataFullNotify(Player player) { + super(PacketOpcodes.CodexDataFullNotify, true); + + //Quests + CodexTypeData.Builder questTypeData = CodexTypeData.newBuilder() + .setTypeValue(1); + + //Tips + CodexTypeData.Builder pushTipsTypeData = CodexTypeData.newBuilder() + .setTypeValue(6); + + //Views + CodexTypeData.Builder viewTypeData = CodexTypeData.newBuilder() + .setTypeValue(7); + + //Weapons + CodexTypeData.Builder weaponTypeData = CodexTypeData.newBuilder() + .setTypeValue(2); + + + player.getQuestManager().forEachMainQuest(mainQuest -> { + if(mainQuest.isFinished()){ + var codexQuest = GameData.getCodexQuestIdMap().get(mainQuest.getParentQuestId()); + if(codexQuest != null){ + questTypeData.addCodexIdList(codexQuest.getId()).addAllHaveViewedList(Collections.singleton(true)); + } + } + }); + + CodexDataFullNotify.Builder proto = CodexDataFullNotify.newBuilder() + .addTypeDataList(questTypeData.build()) + .addTypeDataList(pushTipsTypeData.build()) + .addTypeDataList(viewTypeData.build()) + .addTypeDataList(weaponTypeData); + + this.setData(proto); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataUpdateNotify.java new file mode 100644 index 000000000..c7318bd91 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketCodexDataUpdateNotify.java @@ -0,0 +1,27 @@ +package emu.grasscutter.server.packet.send; + +import java.util.Collections; +import java.util.List; + +import emu.grasscutter.data.GameData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.CodexDataUpdateNotifyOuterClass.CodexDataUpdateNotify; +import emu.grasscutter.server.game.GameSession; + +public class PacketCodexDataUpdateNotify extends BasePacket { + public PacketCodexDataUpdateNotify(GameMainQuest quest) { + super(PacketOpcodes.CodexDataUpdateNotify, true); + var codexQuest = GameData.getCodexQuestIdMap().get(quest.getParentQuestId()); + if(codexQuest != null){ + CodexDataUpdateNotify proto = CodexDataUpdateNotify.newBuilder() + .setTypeValue(1) + .setId(codexQuest.getId()) + .build(); + this.setData(proto); + } + } +} From 82698d56238e84fd5cb5f45ee3181ba639b820b7 Mon Sep 17 00:00:00 2001 From: tester233 <105267106+tester233@users.noreply.github.com> Date: Fri, 13 May 2022 23:36:30 +0800 Subject: [PATCH 51/59] Improve text --- src/main/resources/languages/zh-CN.json | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index 4ece73fd9..aaeac5a86 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -17,7 +17,7 @@ "default_password": "[Dispatch] 成功加载 keystore 默认密码。请考虑将 config.json 的默认密码设置为 123456" }, "authentication": { - "default_unable_to_verify": "[Authentication] 称为 verifyUser 的 method 在默认验证程序中不可用" + "default_unable_to_verify": "[Authentication] 称为 verifyUser 的方法在默认验证程序中不可用" }, "no_commands_error": "此命令不适用于 Dispatch-only 模式", "unhandled_request_error": "[Dispatch] 潜在的未处理请求:%s %s", @@ -215,6 +215,14 @@ "success": "坐标:%s, %s, %s\n场景ID:%s", "description": "获取所在位置" }, + "quest": { + "description": "添加或完成任务", + "usage": "quest [任务ID]", + "added": "已添加任务 %s", + "finished": "已完成任务 %s", + "not_found": "未找到任务", + "invalid_id": "无效的任务ID" + }, "reload": { "reload_start": "正在重载配置文件和数据。", "reload_done": "重载完成。", @@ -356,9 +364,9 @@ "gacha": { "details": { "title": "祈愿详情", - "available_five_stars": "出现的五星物品", - "available_four_stars": "出现的四星物品", - "available_three_stars": "出现的三星物品", + "available_five_stars": "可获得的5星物品", + "available_four_stars": "可获得的4星物品", + "available_three_stars": "可获得的3星物品", "template_missing": "缺失文件:data/gacha_details.html" } } From 248b655f07c96d322fdb865acffa3ddf82da000d Mon Sep 17 00:00:00 2001 From: Melledy <52122272+Melledy@users.noreply.github.com> Date: Fri, 13 May 2022 16:35:03 -0700 Subject: [PATCH 52/59] Use scene types instead of hardcoding scene ids for checking enter reason --- src/main/java/emu/grasscutter/game/world/World.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/world/World.java b/src/main/java/emu/grasscutter/game/world/World.java index 22048077d..ccbe4b841 100644 --- a/src/main/java/emu/grasscutter/game/world/World.java +++ b/src/main/java/emu/grasscutter/game/world/World.java @@ -10,6 +10,7 @@ import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player.SceneLoadState; import emu.grasscutter.game.props.EnterReason; import emu.grasscutter.game.props.EntityIdType; +import emu.grasscutter.game.props.SceneType; import emu.grasscutter.data.GameData; import emu.grasscutter.data.def.DungeonData; import emu.grasscutter.data.def.SceneData; @@ -267,11 +268,9 @@ public class World implements Iterable { enterReason = EnterReason.DungeonEnter; } else if (oldScene == newScene) { enterType = EnterType.ENTER_GOTO; - } - - // Home - if (2001 <= newScene.getId() && newScene.getId() <= 2004) { - enterType = EnterType.ENTER_SELF_HOME; + } else if (newScene.getSceneType() == SceneType.SCENE_HOME_WORLD) { + // Home + enterType = EnterType.ENTER_SELF_HOME; } // Teleport packet From 20e3b8ffda1e695ba479da243e1892ea359d1bf3 Mon Sep 17 00:00:00 2001 From: ShiroSaki <62388797+ShigemoriHakura@users.noreply.github.com> Date: Sat, 14 May 2022 04:31:27 +0800 Subject: [PATCH 53/59] add support for announcement page --- .gitignore | 1 + data/GameAnnouncementList.json | 10 ++- .../server/dispatch/DispatchServer.java | 6 ++ .../http/AnnouncementIndexHandler.java | 61 +++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 src/main/java/emu/grasscutter/server/dispatch/http/AnnouncementIndexHandler.java diff --git a/.gitignore b/.gitignore index 6fd78ed3b..9a298a89a 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,4 @@ BuildConfig.java # macOS .DS_Store +data/hk4e/announcement/ diff --git a/data/GameAnnouncementList.json b/data/GameAnnouncementList.json index f6566960a..7464b3b0f 100644 --- a/data/GameAnnouncementList.json +++ b/data/GameAnnouncementList.json @@ -57,5 +57,13 @@ "mi18n_name": "Activity" } ], - "timezone": -5 + "timezone": -5, + "alert": false, + "alert_id": 0, + "pic_list": [], + "pic_total": 0, + "pic_type_list": [], + "pic_alert": false, + "pic_alert_id": 0, + "static_sign": "" } \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java index 7e439a1a4..7153542a4 100644 --- a/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java +++ b/src/main/java/emu/grasscutter/server/dispatch/DispatchServer.java @@ -16,6 +16,7 @@ import emu.grasscutter.net.proto.RegionInfoOuterClass.RegionInfo; import emu.grasscutter.net.proto.RegionSimpleInfoOuterClass.RegionSimpleInfo; import emu.grasscutter.server.dispatch.authentication.AuthenticationHandler; import emu.grasscutter.server.dispatch.authentication.DefaultAuthenticationHandler; +import emu.grasscutter.server.dispatch.http.AnnouncementIndexHandler; import emu.grasscutter.server.dispatch.http.GachaDetailsHandler; import emu.grasscutter.server.dispatch.http.GachaRecordHandler; import emu.grasscutter.server.dispatch.json.*; @@ -443,6 +444,11 @@ public final class DispatchServer { // gacha details httpServer.get("/gacha/details", new GachaDetailsHandler()); + // announcement index + httpServer.get("/hk4e/announcement/*", new AnnouncementIndexHandler()); + httpServer.get("/sw.js", new AnnouncementIndexHandler()); + httpServer.get("/dora/lib/vue/2.6.11/vue.min.js", new AnnouncementIndexHandler()); + // static file support for plugins httpServer.raw().config.precompressStaticFiles = false; // If this isn't set to false, files such as images may appear corrupted when serving static files diff --git a/src/main/java/emu/grasscutter/server/dispatch/http/AnnouncementIndexHandler.java b/src/main/java/emu/grasscutter/server/dispatch/http/AnnouncementIndexHandler.java new file mode 100644 index 000000000..7e55eac7e --- /dev/null +++ b/src/main/java/emu/grasscutter/server/dispatch/http/AnnouncementIndexHandler.java @@ -0,0 +1,61 @@ +package emu.grasscutter.server.dispatch.http; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.database.DatabaseHelper; +import emu.grasscutter.game.Account; +import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.Utils; +import express.http.HttpContextHandler; +import express.http.Request; +import express.http.Response; + +import java.io.File; +import java.io.IOException; +import java.util.Objects; + +import static emu.grasscutter.Configuration.DATA; + +public class AnnouncementIndexHandler implements HttpContextHandler { + private final String render_template; + private final String render_swjs; + private final String render_vueminjs; + + public AnnouncementIndexHandler() { + File template = new File(Utils.toFilePath(DATA("/hk4e/announcement/index.html"))); + File swjs = new File(Utils.toFilePath(DATA("/hk4e/announcement/sw.js"))); + File vueminjs = new File(Utils.toFilePath(DATA("/hk4e/announcement/vue.min.js"))); + this.render_template = template.exists() ? new String(FileUtils.read(template)) : null; + this.render_swjs = swjs.exists() ? new String(FileUtils.read(swjs)) : null; + this.render_vueminjs = vueminjs.exists() ? new String(FileUtils.read(vueminjs)) : null; + } + + @Override + public void handle(Request req, Response res) throws IOException { + if (Objects.equals(req.path(), "/sw.js")) { + res.send(render_swjs); + }else if(Objects.equals(req.path(), "/hk4e/announcement/index.html")) { + res.send(render_template); + }else if(Objects.equals(req.path(), "/dora/lib/vue/2.6.11/vue.min.js")){ + res.send(render_vueminjs); + }else{ + File renderFile = new File(Utils.toFilePath(DATA(req.path()))); + if(renderFile.exists()){ + String ext = req.path().substring(req.path().lastIndexOf(".") + 1); + switch(ext){ + case "css": + res.type("text/css"); + res.send(FileUtils.read(renderFile)); + break; + case "js": + default: + res.send(FileUtils.read(renderFile)); + break; + } + }else{ + Grasscutter.getLogger().info( "File not exist: " + req.path()); + } + } + + + } +} From 922359d6e6ba7e87ce7e62a24cb07a678c234712 Mon Sep 17 00:00:00 2001 From: muhammadeko Date: Sat, 14 May 2022 07:50:36 +0700 Subject: [PATCH 54/59] add negative permission check --- src/main/java/emu/grasscutter/game/Account.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index 6c3daf61a..39fb8969e 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -144,12 +144,20 @@ public class Account { } public boolean hasPermission(String permission) { - if (this.permissions.contains(permission) || this.permissions.contains("*")) { + if (this.permissions.contains(permission)) { return true; } + if(this.permissions.contains("*") && this.permissions.contains("-"+permission)) { + return false; + } + String[] permissionParts = permission.split("\\."); for (String p : this.permissions) { + if (p.startsWith("-") && permissionMatchesWildcard(p.substring(1), permissionParts)) { + return false; + } if (permissionMatchesWildcard(p, permissionParts)) { + Grasscutter.getLogger().info("Permission " + p + " matches " + permission); return true; } } From 855a098aa7210ab78bd6d78d7d8855ccd09ee461 Mon Sep 17 00:00:00 2001 From: muhammadeko Date: Sat, 14 May 2022 11:32:33 +0700 Subject: [PATCH 55/59] fix logic and some cleaning --- .../java/emu/grasscutter/game/Account.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index 39fb8969e..518bd4786 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -144,24 +144,22 @@ public class Account { } public boolean hasPermission(String permission) { - if (this.permissions.contains(permission)) { - return true; - } - if(this.permissions.contains("*") && this.permissions.contains("-"+permission)) { - return false; - } + + if (this.permissions.contains(permission)) return true; + if(this.permissions.contains("*") && this.permissions.size() == 1) return true; String[] permissionParts = permission.split("\\."); for (String p : this.permissions) { + if (p.startsWith("-") && permissionMatchesWildcard(p.substring(1), permissionParts)) { + Grasscutter.getLogger().info("Permission " + permission + " denied to " + this.username); return false; } - if (permissionMatchesWildcard(p, permissionParts)) { - Grasscutter.getLogger().info("Permission " + p + " matches " + permission); - return true; - } + + if (permissionMatchesWildcard(p, permissionParts)) return true; } - return false; + + return this.permissions.contains("*"); } public boolean removePermission(String permission) { From 4f553f6694f53dba4c0b5bba6ffa6caee72a2b9f Mon Sep 17 00:00:00 2001 From: muhammadeko Date: Sat, 14 May 2022 11:34:09 +0700 Subject: [PATCH 56/59] remove log --- src/main/java/emu/grasscutter/game/Account.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/emu/grasscutter/game/Account.java b/src/main/java/emu/grasscutter/game/Account.java index 518bd4786..84873ec61 100644 --- a/src/main/java/emu/grasscutter/game/Account.java +++ b/src/main/java/emu/grasscutter/game/Account.java @@ -150,12 +150,7 @@ public class Account { String[] permissionParts = permission.split("\\."); for (String p : this.permissions) { - - if (p.startsWith("-") && permissionMatchesWildcard(p.substring(1), permissionParts)) { - Grasscutter.getLogger().info("Permission " + permission + " denied to " + this.username); - return false; - } - + if (p.startsWith("-") && permissionMatchesWildcard(p.substring(1), permissionParts)) return false; if (permissionMatchesWildcard(p, permissionParts)) return true; } From 3c654cf0302b4b43b0315ae39607fccf5bcf5951 Mon Sep 17 00:00:00 2001 From: luluxiaoyu <58876608+luluxiaoyu@users.noreply.github.com> Date: Sat, 14 May 2022 09:30:40 +0800 Subject: [PATCH 57/59] Update zh-TW.json --- src/main/resources/languages/zh-TW.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index 2b6fe34ff..caf37d0d7 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -215,6 +215,14 @@ "success": "座標:%s, %s, %s\n場景ID:%s", "description": "獲取目前所在位置的座標。" }, + "quest": { + "description": "添加或完成任務", + "usage": "quest [任務ID]", + "added": "已添加任務 %s", + "finished": "已完成任務 %s", + "not_found": "未找到任務", + "invalid_id": "無效的任務ID" + }, "reload": { "reload_start": "正在重新加載設定檔。", "reload_done": "重新加載已完成。", From 470007a6c0bc9b117985d4de973b7193d0ff737f Mon Sep 17 00:00:00 2001 From: zrll_ <46812903+zrll12@users.noreply.github.com> Date: Sat, 14 May 2022 15:12:57 +0800 Subject: [PATCH 58/59] Fix connot execute quest command in console --- .../java/emu/grasscutter/command/commands/QuestCommand.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/emu/grasscutter/command/commands/QuestCommand.java b/src/main/java/emu/grasscutter/command/commands/QuestCommand.java index 70fae0120..affbfa769 100644 --- a/src/main/java/emu/grasscutter/command/commands/QuestCommand.java +++ b/src/main/java/emu/grasscutter/command/commands/QuestCommand.java @@ -37,7 +37,7 @@ public final class QuestCommand implements CommandHandler { switch (cmd) { case "add" -> { - GameQuest quest = sender.getQuestManager().addQuest(questId); + GameQuest quest = targetPlayer.getQuestManager().addQuest(questId); if (quest != null) { CommandHandler.sendMessage(sender, translate(sender, "commands.quest.added", questId)); @@ -47,7 +47,7 @@ public final class QuestCommand implements CommandHandler { CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found")); } case "finish" -> { - GameQuest quest = sender.getQuestManager().getQuestById(questId); + GameQuest quest = targetPlayer.getQuestManager().getQuestById(questId); if (quest == null) { CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found")); From 741e5749ad4b242efce1ad42dea903136f7ccd4f Mon Sep 17 00:00:00 2001 From: lsCoding666 <73888354+lsCoding666@users.noreply.github.com> Date: Sat, 14 May 2022 21:28:02 +0800 Subject: [PATCH 59/59] new command join and remove to force join or remove avatars into your current team (#549) * feat:new command "join" and "remove" to force join or remove avatar in your current team * fix:change MaxAvatarsInTeam from 9 to 4 * feat:update & merge branch.Translate fix --- .../command/commands/JoinCommand.java | 46 +++++++++++++++++++ .../command/commands/RemoveCommand.java | 43 +++++++++++++++++ src/main/resources/languages/en-US.json | 9 ++++ src/main/resources/languages/zh-CN.json | 9 ++++ src/main/resources/languages/zh-TW.json | 5 ++ 5 files changed, 112 insertions(+) create mode 100644 src/main/java/emu/grasscutter/command/commands/JoinCommand.java create mode 100644 src/main/java/emu/grasscutter/command/commands/RemoveCommand.java diff --git a/src/main/java/emu/grasscutter/command/commands/JoinCommand.java b/src/main/java/emu/grasscutter/command/commands/JoinCommand.java new file mode 100644 index 000000000..4dac15dd7 --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/JoinCommand.java @@ -0,0 +1,46 @@ +package emu.grasscutter.command.commands; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.command.Command; +import emu.grasscutter.command.CommandHandler; +import emu.grasscutter.game.avatar.Avatar; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.server.packet.send.PacketChangeMpTeamAvatarRsp; + +import java.util.ArrayList; +import java.util.List; + +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "join", usage = "join [AvatarIDs] such as\"join 10000038 10000039\"", + description = "commands.join.description", permission = "player.join") +public class JoinCommand implements CommandHandler { + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + List avatarIds = new ArrayList<>(); + for (String arg : args) { + try { + int avatarId = Integer.parseInt(arg); + avatarIds.add(avatarId); + } catch (Exception ignored) { + ignored.printStackTrace(); + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.avatarId")); + return; + } + } + + + for (int i = 0; i < args.size(); i++) { + Avatar avatar = sender.getAvatars().getAvatarById(avatarIds.get(i)); + if (avatar == null || sender.getTeamManager().getCurrentTeamInfo().contains(avatar)) { + CommandHandler.sendMessage(sender, translate("commands.generic.invalid.avatarId")); + return; + } + sender.getTeamManager().getCurrentTeamInfo().addAvatar(avatar); + } + + // Packet + sender.getTeamManager().updateTeamEntities(new PacketChangeMpTeamAvatarRsp(sender, sender.getTeamManager().getCurrentTeamInfo())); + } +} diff --git a/src/main/java/emu/grasscutter/command/commands/RemoveCommand.java b/src/main/java/emu/grasscutter/command/commands/RemoveCommand.java new file mode 100644 index 000000000..a40b42698 --- /dev/null +++ b/src/main/java/emu/grasscutter/command/commands/RemoveCommand.java @@ -0,0 +1,43 @@ +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.PacketChangeMpTeamAvatarRsp; + +import java.util.ArrayList; +import java.util.List; + +import static emu.grasscutter.utils.Language.translate; + +@Command(label = "remove", usage = "remove [indexOfYourTeams] index start from 1", + description = "commands.remove.description", permission = "player.remove") +public class RemoveCommand implements CommandHandler { + + @Override + public void execute(Player sender, Player targetPlayer, List args) { + List avatarIds = new ArrayList<>(); + for (String arg : args) { + try { + int avatarId = Integer.parseInt(arg); + avatarIds.add(avatarId); + } catch (Exception ignored) { + ignored.printStackTrace(); + CommandHandler.sendMessage(sender, translate("commands.remove.invalid_index")); + return; + } + } + + for (int i = 0; i < avatarIds.size(); i++) { + if (avatarIds.get(i) > sender.getTeamManager().getCurrentTeamInfo().getAvatars().size() || avatarIds.get(i) <= 0) { + CommandHandler.sendMessage(sender, translate("commands.remove.invalid_index")); + return; + } + sender.getTeamManager().getCurrentTeamInfo().removeAvatar(avatarIds.get(i) - 1); + } + + // Packet + sender.getTeamManager().updateTeamEntities(new PacketChangeMpTeamAvatarRsp(sender, sender.getTeamManager().getCurrentTeamInfo())); + } +} diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 02945e6d4..32631db1f 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -176,6 +176,10 @@ "success": "All characters have been healed.", "description": "Heal all characters in your current team." }, + "join": { + "usage": "Usage: join [AvatarIDs] such as\"join 10000038 10000039\"", + "description": "force join avatar into your team" + }, "kick": { "player_kick_player": "Player [%s:%s] has kicked player [%s:%s]", "server_kick_player": "Kicking player [%s:%s]", @@ -228,6 +232,11 @@ "reload_done": "Reload complete.", "description": "Reload server config" }, + "remove": { + "usage": "Usage: remove [indexOfYourTeams] index start from 1", + "invalid_index": "index start from 1", + "description": "force remove avatar into your team" + }, "resetConst": { "reset_all": "Reset all avatars' constellations.", "success": "Constellations for %s have been reset. Please relog to see changes.", diff --git a/src/main/resources/languages/zh-CN.json b/src/main/resources/languages/zh-CN.json index aaeac5a86..5f379540b 100644 --- a/src/main/resources/languages/zh-CN.json +++ b/src/main/resources/languages/zh-CN.json @@ -176,6 +176,10 @@ "success": "已治疗所有角色。", "description": "治疗当前队伍的角色" }, + "join": { + "usage": "用法:join <角色IDs> 例如\"join 10000038 10000039\"空格分开", + "description": "强制将角色加入到当前队伍中" + }, "kick": { "player_kick_player": "玩家 [%s:%s] 已将 [%s:%s] 踢出。", "server_kick_player": "正在踢出玩家 [%s:%s]...", @@ -228,6 +232,11 @@ "reload_done": "重载完成。", "description": "重载配置文件和数据" }, + "remove": { + "usage": "用法: remove [indexOfYourTeams] 从1开始", + "invalid_index": "下标从1开始", + "description": "强制移除队内角色" + }, "resetConst": { "reset_all": "重置所有角色的命座。", "success": "已重置 %s 的命座,重新登录后生效。", diff --git a/src/main/resources/languages/zh-TW.json b/src/main/resources/languages/zh-TW.json index caf37d0d7..25f92f869 100644 --- a/src/main/resources/languages/zh-TW.json +++ b/src/main/resources/languages/zh-TW.json @@ -228,6 +228,11 @@ "reload_done": "重新加載已完成。", "description": "重新加載設定檔和數據。" }, + "remove": { + "usage": "用法: remove [indexOfYourTeams] 从1开始", + "invalid_index": "下標從1開始", + "description": "强制移除對内角色" + }, "resetConst": { "reset_all": "重設所有角色的命座。", "success": "已重設 %s 的命座,重新登入後將會生效。",