diff --git a/proto/ChildQuest.proto b/proto/ChildQuest.proto new file mode 100644 index 000000000..fcec288c1 --- /dev/null +++ b/proto/ChildQuest.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message ChildQuest { + uint32 quest_id = 1; + uint32 state = 2; + uint32 quest_config_id = 3; +} diff --git a/proto/CutSceneBeginNotify.proto b/proto/CutSceneBeginNotify.proto new file mode 100644 index 000000000..9a926541c --- /dev/null +++ b/proto/CutSceneBeginNotify.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message CutSceneBeginNotify { + uint32 cutscene_id = 1; + bool is_wait_others = 2; +} diff --git a/proto/CutSceneEndNotify.proto b/proto/CutSceneEndNotify.proto new file mode 100644 index 000000000..c3f91a4e1 --- /dev/null +++ b/proto/CutSceneEndNotify.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message CutSceneEndNotify { + int32 retcode = 1; + uint32 cutscene_id = 2; +} diff --git a/proto/CutSceneFinishNotify.proto b/proto/CutSceneFinishNotify.proto new file mode 100644 index 000000000..8c42d8536 --- /dev/null +++ b/proto/CutSceneFinishNotify.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message CutSceneFinishNotify { + uint32 cutscene_id = 1; +} diff --git a/proto/FinishedParentQuestNotify.proto b/proto/FinishedParentQuestNotify.proto new file mode 100644 index 000000000..834b18a47 --- /dev/null +++ b/proto/FinishedParentQuestNotify.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "ParentQuest.proto"; + +message FinishedParentQuestNotify { + repeated ParentQuest parent_quest_list = 1; +} diff --git a/proto/FinishedParentQuestUpdateNotify.proto b/proto/FinishedParentQuestUpdateNotify.proto new file mode 100644 index 000000000..82565af5c --- /dev/null +++ b/proto/FinishedParentQuestUpdateNotify.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "ParentQuest.proto"; + +message FinishedParentQuestUpdateNotify { + repeated ParentQuest parent_quest_list = 1; +} diff --git a/proto/ParentQuest.proto b/proto/ParentQuest.proto new file mode 100644 index 000000000..477366e6c --- /dev/null +++ b/proto/ParentQuest.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "ParentQuestRandomInfo.proto"; +import "ChildQuest.proto"; + +message ParentQuest { + uint32 parent_quest_id = 1; + repeated ChildQuest child_quest_list = 2; + bool is_finished = 3; + bool is_random = 4; + ParentQuestRandomInfo random_info = 5; + repeated int32 quest_var = 6; + uint32 parent_quest_state = 7; + uint32 quest_var_seq = 8; + map time_var_map = 9; +} diff --git a/proto/ParentQuestRandomInfo.proto b/proto/ParentQuestRandomInfo.proto new file mode 100644 index 000000000..61f04a74f --- /dev/null +++ b/proto/ParentQuestRandomInfo.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message ParentQuestRandomInfo { + uint32 entrance_id = 1; + uint32 template_id = 2; + repeated uint32 factor_list = 3; +} diff --git a/proto/Quest.proto b/proto/Quest.proto new file mode 100644 index 000000000..a8b8e9adc --- /dev/null +++ b/proto/Quest.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message Quest { + uint32 quest_id = 1; + uint32 state = 2; + uint32 start_time = 4; + bool is_random = 5; + uint32 parent_quest_id = 6; + uint32 quest_config_id = 7; + uint32 start_game_time = 8; + uint32 accept_time = 9; + repeated uint32 lacked_npc_list = 10; + repeated uint32 finish_progress_list = 11; + repeated uint32 fail_progress_list = 12; + map lacked_npc_map = 13; + repeated uint32 lacked_place_list = 14; + map lacked_place_map = 15; +} diff --git a/proto/QuestDelNotify.proto b/proto/QuestDelNotify.proto new file mode 100644 index 000000000..0365ec303 --- /dev/null +++ b/proto/QuestDelNotify.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message QuestDelNotify { + uint32 quest_id = 1; +} diff --git a/proto/QuestGlobalVar.proto b/proto/QuestGlobalVar.proto new file mode 100644 index 000000000..c5df4ac9f --- /dev/null +++ b/proto/QuestGlobalVar.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message QuestGlobalVar { + uint32 key = 1; + int32 value = 2; +} diff --git a/proto/QuestGlobalVarNotify.proto b/proto/QuestGlobalVarNotify.proto new file mode 100644 index 000000000..0803f348c --- /dev/null +++ b/proto/QuestGlobalVarNotify.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "QuestGlobalVar.proto"; + +message QuestGlobalVarNotify { + repeated QuestGlobalVar var_list = 1; +} diff --git a/proto/QuestListNotify.proto b/proto/QuestListNotify.proto new file mode 100644 index 000000000..ae40ba1aa --- /dev/null +++ b/proto/QuestListNotify.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "Quest.proto"; + +message QuestListNotify { + repeated Quest quest_list = 1; +} diff --git a/proto/QuestListUpdateNotify.proto b/proto/QuestListUpdateNotify.proto new file mode 100644 index 000000000..5e78079b6 --- /dev/null +++ b/proto/QuestListUpdateNotify.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "Quest.proto"; + +message QuestListUpdateNotify { + repeated Quest quest_list = 1; +} diff --git a/proto/QuestProgressUpdateNotify.proto b/proto/QuestProgressUpdateNotify.proto new file mode 100644 index 000000000..ac3fccd32 --- /dev/null +++ b/proto/QuestProgressUpdateNotify.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message QuestProgressUpdateNotify { + uint32 quest_id = 1; + repeated uint32 finish_progress_list = 2; + repeated uint32 fail_progress_list = 3; +} diff --git a/proto/QuestUpdateQuestVarNotify.proto b/proto/QuestUpdateQuestVarNotify.proto new file mode 100644 index 000000000..ba61ac4c0 --- /dev/null +++ b/proto/QuestUpdateQuestVarNotify.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message QuestUpdateQuestVarNotify { + uint32 parent_quest_id = 1; + repeated int32 quest_var = 2; + uint32 parent_quest_var_seq = 3; +} diff --git a/proto/QuestUpdateQuestVarReq.proto b/proto/QuestUpdateQuestVarReq.proto new file mode 100644 index 000000000..c89e7f0e7 --- /dev/null +++ b/proto/QuestUpdateQuestVarReq.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +import "QuestVarOp.proto"; + +message QuestUpdateQuestVarReq { + uint32 quest_id = 1; + repeated QuestVarOp quest_var_op_list = 2; + uint32 parent_quest_id = 3; + uint32 parent_quest_var_seq = 4; +} diff --git a/proto/QuestUpdateQuestVarRsp.proto b/proto/QuestUpdateQuestVarRsp.proto new file mode 100644 index 000000000..6f28cfb59 --- /dev/null +++ b/proto/QuestUpdateQuestVarRsp.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message QuestUpdateQuestVarRsp { + int32 retcode = 1; + uint32 quest_id = 2; + uint32 parent_quest_id = 3; + uint32 parent_quest_var_seq = 4; +} diff --git a/proto/QuestVarOp.proto b/proto/QuestVarOp.proto new file mode 100644 index 000000000..51d4411b9 --- /dev/null +++ b/proto/QuestVarOp.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message QuestVarOp { + uint32 index = 1; + int32 value = 2; + bool is_add = 3; +} diff --git a/proto/ServerCondMeetQuestListUpdateNotify.proto b/proto/ServerCondMeetQuestListUpdateNotify.proto new file mode 100644 index 000000000..4326518ae --- /dev/null +++ b/proto/ServerCondMeetQuestListUpdateNotify.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option java_package = "emu.grasscutter.net.proto"; + +message ServerCondMeetQuestListUpdateNotify { + repeated uint32 add_quest_id_list = 1; + repeated uint32 del_quest_id_list = 2; +} 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.",