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..75b840202 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.MainQuestData; 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 mainQuestData = new Int2ObjectOpenHashMap<>(); // ExcelConfigs private static final Int2ObjectMap playerLevelDataMap = new Int2ObjectOpenHashMap<>(); @@ -68,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<>(); @@ -122,6 +125,10 @@ public class GameData { return getScenePointEntries().get(sceneId + "_" + pointId); } + public static Int2ObjectMap getMainQuestDataMap() { + return mainQuestData; + } + public static Int2ObjectMap getAvatarDataMap() { return avatarDataMap; } @@ -331,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 844bbec5e..4b940c44d 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -24,6 +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.MainQuestData; import emu.grasscutter.data.custom.ScenePointEntry; import emu.grasscutter.game.world.SpawnDataEntry.*; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -58,8 +59,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 @@ -394,6 +396,29 @@ public class ResourceLoader { GameData.getOpenConfigEntries().put(entry.getName(), entry); } } + + private static void loadQuests() { + File folder = new File(RESOURCE("BinOutput/Quest/")); + + if (!folder.exists()) { + return; + } + + for (File file : folder.listFiles()) { + MainQuestData mainQuest = null; + + try (FileReader fileReader = new FileReader(file)) { + mainQuest = Grasscutter.getGsonFactory().fromJson(fileReader, MainQuestData.class); + } catch (Exception e) { + e.printStackTrace(); + continue; + } + + GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest); + } + + 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/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/database/DatabaseHelper.java b/src/main/java/emu/grasscutter/database/DatabaseHelper.java index 5b622aac8..f89779fee 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.getGameDatabase().getCollection("gachas").deleteMany(eq("ownerId", target.getPlayerUid())); // Delete GameItem.class data DatabaseManager.getGameDatabase().getCollection("items").deleteMany(eq("ownerId", target.getPlayerUid())); + // Delete GameMainQuest.class data + DatabaseManager.getGameDatabase().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.getGameDatastore().delete(mail); return result.wasAcknowledged(); } + + public static List getAllQuests(Player player) { + return DatabaseManager.getGameDatastore().find(GameMainQuest.class).filter(Filters.eq("ownerUid", player.getUid())).stream().toList(); + } + + public static void saveQuest(GameMainQuest quest) { + DatabaseManager.getGameDatastore().save(quest); + } + + public static boolean deleteQuest(GameMainQuest quest) { + return DatabaseManager.getGameDatastore().delete(quest).wasAcknowledged(); + } } diff --git a/src/main/java/emu/grasscutter/database/DatabaseManager.java b/src/main/java/emu/grasscutter/database/DatabaseManager.java index 37ec0094d..7c1360f7c 100644 --- a/src/main/java/emu/grasscutter/database/DatabaseManager.java +++ b/src/main/java/emu/grasscutter/database/DatabaseManager.java @@ -19,6 +19,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; import static emu.grasscutter.Configuration.*; @@ -27,7 +29,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 getGameDatastore() { 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/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index f27d68a29..b7d8470bf 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; @@ -95,6 +98,7 @@ public class Player { @Transient private MailHandler mailHandler; @Transient private MessageHandler messageHandler; @Transient private AbilityManager abilityManager; + @Transient private QuestManager questManager; @Transient private SotSManager sotsManager; @@ -150,6 +154,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<>(); @@ -422,6 +427,14 @@ public class Player { return towerManager; } + public QuestManager getQuestManager() { + return questManager; + } + + public void setQuestManager(QuestManager questManager) { + this.questManager = questManager; + } + public PlayerGachaInfo getGachaInfo() { return gachaInfo; } @@ -896,9 +909,7 @@ public class Player { } public void sendPacket(BasePacket packet) { - if (this.hasSentAvatarDataNotify) { - this.getSession().send(packet); - } + this.getSession().send(packet); } public OnlinePlayerInfo getOnlinePlayerInfo() { @@ -1135,7 +1146,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); @@ -1155,7 +1182,9 @@ 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)); 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 new file mode 100644 index 000000000..bf88b8efe --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java @@ -0,0 +1,123 @@ +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.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..b242166eb --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/GameQuest.java @@ -0,0 +1,215 @@ +package emu.grasscutter.game.quest; + +import dev.morphia.annotations.Entity; +import dev.morphia.annotations.Transient; +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; +import emu.grasscutter.net.proto.QuestOuterClass.Quest; +import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify; +import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify; +import emu.grasscutter.utils.Utils; + +@Entity +public class GameQuest { + @Transient private GameMainQuest mainQuest; + @Transient private QuestData questData; + + 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, QuestData questData) { + this.mainQuest = mainQuest; + 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 (questData.getFinishCond()!= null) { + this.finishProgressList = new int[questData.getFinishCond().length]; + } + + if (questData.getFailCond() != null) { + this.failProgressList = new int[questData.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 QuestData getData() { + return questData; + } + + public void setConfig(QuestData config) { + if (this.getQuestId() != config.getId()) return; + this.questData = 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)); + this.getOwner().getSession().send(new PacketQuestListUpdateNotify(this)); + this.save(); + + this.tryAcceptQuestLine(); + } + + 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) { + 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, condition.getParam()); + + accept[i] = result ? 1 : 0; + } + + boolean shouldAccept = LogicType.calculate(questData.getAcceptCondComb(), accept); + + if (shouldAccept) { + this.getOwner().getQuestManager().addQuest(questData.getId()); + } + } + } + } 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..548e8241a --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/QuestManager.java @@ -0,0 +1,182 @@ +package emu.grasscutter.game.quest; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; + +import emu.grasscutter.data.GameData; +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.QuestTrigger; +import emu.grasscutter.game.quest.enums.LogicType; +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.PacketQuestProgressUpdateNotify; +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) { + QuestData questConfig = GameData.getQuestDataMap().get(questId); + if (questConfig == null) { + return null; + } + + GameMainQuest mainQuest = getQuests().get(questConfig.getMainId()); + + 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); + } + } + } + + // TODO + public void forEachActiveQuest(Consumer callback) { + for (GameMainQuest mainQuest : getQuests().values()) { + for (GameQuest quest : mainQuest.getChildQuests().values()) { + if (quest.getState() != QuestState.QUEST_STATE_FINISHED) { + callback.accept(quest); + } + } + } + } + + public GameMainQuest addMainQuest(QuestData questConfig) { + GameMainQuest mainQuest = new GameMainQuest(getPlayer(), questConfig.getMainId()); + getQuests().put(mainQuest.getParentQuestId(), mainQuest); + + getPlayer().sendPacket(new PacketFinishedParentQuestUpdateNotify(mainQuest)); + + return mainQuest; + } + + public GameQuest addQuest(int questId) { + QuestData questConfig = GameData.getQuestDataMap().get(questId); + if (questConfig == null) { + return null; + } + + // Main quest + GameMainQuest mainQuest = this.getMainQuestById(questConfig.getMainId()); + + // 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 triggerEvent(QuestTrigger condType, int... params) { + Set changedQuests = new HashSet<>(); + + this.forEachActiveQuest(quest -> { + QuestData data = quest.getData(); + + for (int i = 0; i < data.getFinishCond().length; i++) { + if (quest.getFinishProgressList()[i] == 1) { + continue; + } + + QuestCondition condition = data.getFinishCond()[i]; + + if (condition.getType() != condType) { + continue; + } + + boolean result = getPlayer().getServer().getQuestHandler().triggerContent(quest, condition, params); + + if (result) { + quest.getFinishProgressList()[i] = 1; + + changedQuests.add(quest); + } + } + }); + + for (GameQuest quest : changedQuests) { + LogicType logicType = quest.getData().getFailCondComb(); + int[] progress = quest.getFinishProgressList(); + + // Handle logical comb + boolean finish = LogicType.calculate(logicType, progress); + + // Finish + if (finish) { + quest.finish(); + } else { + getPlayer().sendPacket(new PacketQuestProgressUpdateNotify(quest)); + quest.save(); + } + } + } + + 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.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 new file mode 100644 index 000000000..42b868fc8 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/QuestValue.java @@ -0,0 +1,11 @@ +package emu.grasscutter.game.quest; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import emu.grasscutter.game.quest.enums.QuestTrigger; + +@Retention(RetentionPolicy.RUNTIME) +public @interface QuestValue { + QuestTrigger value(); +} diff --git a/src/main/java/emu/grasscutter/game/quest/ServerQuestHandler.java b/src/main/java/emu/grasscutter/game/quest/ServerQuestHandler.java new file mode 100644 index 000000000..36c929ab3 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/ServerQuestHandler.java @@ -0,0 +1,89 @@ +package emu.grasscutter.game.quest; + +import java.util.Set; + +import org.reflections.Reflections; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.def.QuestData.QuestCondition; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +@SuppressWarnings("unchecked") +public class ServerQuestHandler { + private final Int2ObjectMap condHandlers; + private final Int2ObjectMap contHandlers; + private final Int2ObjectMap execHandlers; + + public ServerQuestHandler() { + this.condHandlers = new Int2ObjectOpenHashMap<>(); + this.contHandlers = new Int2ObjectOpenHashMap<>(); + this.execHandlers = new Int2ObjectOpenHashMap<>(); + + this.registerHandlers(); + } + + public void registerHandlers() { + this.registerHandlers(this.condHandlers, "emu.grasscutter.game.quest.conditions"); + this.registerHandlers(this.contHandlers, "emu.grasscutter.game.quest.content"); + this.registerHandlers(this.execHandlers, "emu.grasscutter.game.quest.exec"); + } + + public void registerHandlers(Int2ObjectMap map, String packageName) { + Reflections reflections = new Reflections(packageName); + Set handlerClasses = reflections.getSubTypesOf(QuestBaseHandler.class); + + for (Object obj : handlerClasses) { + this.registerPacketHandler(map, (Class) obj); + } + } + + public void registerPacketHandler(Int2ObjectMap map, Class handlerClass) { + try { + QuestValue opcode = handlerClass.getAnnotation(QuestValue.class); + + if (opcode == null || opcode.value().getValue() <= 0) { + return; + } + + QuestBaseHandler packetHandler = (QuestBaseHandler) handlerClass.newInstance(); + + map.put(opcode.value().getValue(), packetHandler); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // TODO make cleaner + + public boolean triggerCondition(GameQuest quest, QuestCondition condition, int... params) { + QuestBaseHandler handler = condHandlers.get(condition.getType().getValue()); + + if (handler == null || quest.getData() == null) { + return false; + } + + return handler.execute(quest, condition, params); + } + + public boolean triggerContent(GameQuest quest, QuestCondition condition, int... params) { + QuestBaseHandler handler = contHandlers.get(condition.getType().getValue()); + + if (handler == null || quest.getData() == null) { + return false; + } + + return handler.execute(quest, condition, params); + } + + public boolean triggerExec(GameQuest quest, QuestCondition condition, int... params) { + QuestBaseHandler handler = execHandlers.get(condition.getType().getValue()); + + if (handler == null || quest.getData() == null) { + return false; + } + + return handler.execute(quest, condition, params); + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/BaseCondition.java b/src/main/java/emu/grasscutter/game/quest/conditions/BaseCondition.java new file mode 100644 index 000000000..d94e60c22 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/conditions/BaseCondition.java @@ -0,0 +1,18 @@ +package emu.grasscutter.game.quest.conditions; + +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_NONE) +public class BaseCondition extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestCondition condition, int... params) { + // TODO Auto-generated method stub + return false; + } + +} diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPlayerLevelEqualGreater.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPlayerLevelEqualGreater.java new file mode 100644 index 000000000..3e3db87fb --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionPlayerLevelEqualGreater.java @@ -0,0 +1,17 @@ +package emu.grasscutter.game.quest.conditions; + +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_COND_PLAYER_LEVEL_EQUAL_GREATER) +public class ConditionPlayerLevelEqualGreater extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestCondition condition, int... params) { + 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 new file mode 100644 index 000000000..37ecc6d30 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java @@ -0,0 +1,23 @@ +package emu.grasscutter.game.quest.conditions; + +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_COND_STATE_EQUAL) +public class ConditionStateEqual extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestCondition condition, int... params) { + GameQuest checkQuest = quest.getOwner().getQuestManager().getQuestById(params[0]); + + if (checkQuest != null) { + 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 new file mode 100644 index 000000000..ce700896d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/BaseContent.java @@ -0,0 +1,18 @@ +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_NONE) +public class BaseContent extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestCondition condition, int... params) { + // TODO Auto-generated method stub + return false; + } + +} diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java b/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java new file mode 100644 index 000000000..3423519ec --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.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_COMPLETE_TALK) +public class ContentCompleteTalk 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/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]; + } + +} 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..12677ee0d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/LogicType.java @@ -0,0 +1,43 @@ +package emu.grasscutter.game.quest.enums; + +import java.util.Arrays; + +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; + } + + public static boolean calculate(LogicType logicType, int[] progress) { + if (logicType == null) { + return progress[0] == 1; + } + + switch (logicType) { + case LOGIC_AND -> { + return Arrays.stream(progress).allMatch(i -> i == 1); + } + case LOGIC_OR -> { + return Arrays.stream(progress).anyMatch(i -> i == 1); + } + default -> { + return Arrays.stream(progress).anyMatch(i -> i == 1); + } + } + } +} 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/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/QuestTrigger.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestTrigger.java new file mode 100644 index 000000000..def3a399d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestTrigger.java @@ -0,0 +1,235 @@ +package emu.grasscutter.game.quest.enums; + +public enum QuestTrigger { + 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), + + QUEST_CONTENT_NONE (0), + QUEST_CONTENT_KILL_MONSTER (1), + QUEST_CONTENT_COMPLETE_TALK (2), + QUEST_CONTENT_MONSTER_DIE (3), + QUEST_CONTENT_FINISH_PLOT (4), + QUEST_CONTENT_OBTAIN_ITEM (5), + QUEST_CONTENT_TRIGGER_FIRE (6), + QUEST_CONTENT_CLEAR_GROUP_MONSTER (7), + QUEST_CONTENT_NOT_FINISH_PLOT (8), + QUEST_CONTENT_ENTER_DUNGEON (9), + QUEST_CONTENT_ENTER_MY_WORLD (10), + QUEST_CONTENT_FINISH_DUNGEON (11), + QUEST_CONTENT_DESTROY_GADGET (12), + QUEST_CONTENT_OBTAIN_MATERIAL_WITH_SUBTYPE (13), + QUEST_CONTENT_NICK_NAME (14), + QUEST_CONTENT_WORKTOP_SELECT (15), + QUEST_CONTENT_SEAL_BATTLE_RESULT (16), + QUEST_CONTENT_ENTER_ROOM (17), + QUEST_CONTENT_GAME_TIME_TICK (18), + QUEST_CONTENT_FAIL_DUNGEON (19), + QUEST_CONTENT_LUA_NOTIFY (20), + QUEST_CONTENT_TEAM_DEAD (21), + QUEST_CONTENT_COMPLETE_ANY_TALK (22), + QUEST_CONTENT_UNLOCK_TRANS_POINT (23), + QUEST_CONTENT_ADD_QUEST_PROGRESS (24), + QUEST_CONTENT_INTERACT_GADGET (25), + QUEST_CONTENT_DAILY_TASK_COMP_FINISH (26), + QUEST_CONTENT_FINISH_ITEM_GIVING (27), + QUEST_CONTENT_SKILL (107), + QUEST_CONTENT_CITY_LEVEL_UP (109), + QUEST_CONTENT_PATTERN_GROUP_CLEAR_MONSTER (110), + QUEST_CONTENT_ITEM_LESS_THAN (111), + QUEST_CONTENT_PLAYER_LEVEL_UP (112), + QUEST_CONTENT_DUNGEON_OPEN_STATUE (113), + QUEST_CONTENT_UNLOCK_AREA (114), + QUEST_CONTENT_OPEN_CHEST_WITH_GADGET_ID (115), + QUEST_CONTENT_UNLOCK_TRANS_POINT_WITH_TYPE (116), + QUEST_CONTENT_FINISH_DAILY_DUNGEON (117), + QUEST_CONTENT_FINISH_WEEKLY_DUNGEON (118), + QUEST_CONTENT_QUEST_VAR_EQUAL (119), + QUEST_CONTENT_QUEST_VAR_GREATER (120), + QUEST_CONTENT_QUEST_VAR_LESS (121), + QUEST_CONTENT_OBTAIN_VARIOUS_ITEM (122), + QUEST_CONTENT_FINISH_TOWER_LEVEL (123), + QUEST_CONTENT_BARGAIN_SUCC (124), + QUEST_CONTENT_BARGAIN_FAIL (125), + QUEST_CONTENT_ITEM_LESS_THAN_BARGAIN (126), + QUEST_CONTENT_ACTIVITY_TRIGGER_FAILED (127), + QUEST_CONTENT_MAIN_COOP_ENTER_SAVE_POINT (128), + QUEST_CONTENT_ANY_MANUAL_TRANSPORT (129), + QUEST_CONTENT_USE_ITEM (130), + QUEST_CONTENT_MAIN_COOP_ENTER_ANY_SAVE_POINT (131), + QUEST_CONTENT_ENTER_MY_HOME_WORLD (132), + QUEST_CONTENT_ENTER_MY_WORLD_SCENE (133), + QUEST_CONTENT_TIME_VAR_GT_EQ (134), + QUEST_CONTENT_TIME_VAR_PASS_DAY (135), + QUEST_CONTENT_QUEST_STATE_EQUAL (136), + QUEST_CONTENT_QUEST_STATE_NOT_EQUAL (137), + QUEST_CONTENT_UNLOCKED_RECIPE (138), + QUEST_CONTENT_NOT_UNLOCKED_RECIPE (139), + QUEST_CONTENT_FISHING_SUCC (140), + QUEST_CONTENT_ENTER_ROGUE_DUNGEON (141), + QUEST_CONTENT_USE_WIDGET (142), + QUEST_CONTENT_CAPTURE_SUCC (143), + QUEST_CONTENT_CAPTURE_USE_CAPTURETAG_LIST (144), + QUEST_CONTENT_CAPTURE_USE_MATERIAL_LIST (145), + QUEST_CONTENT_ENTER_VEHICLE (147), + QUEST_CONTENT_SCENE_LEVEL_TAG_EQ (148), + QUEST_CONTENT_LEAVE_SCENE (149), + QUEST_CONTENT_LEAVE_SCENE_RANGE (150), + QUEST_CONTENT_IRODORI_FINISH_FLOWER_COMBINATION (151), + QUEST_CONTENT_IRODORI_POETRY_REACH_MIN_PROGRESS (152), + QUEST_CONTENT_IRODORI_POETRY_FINISH_FILL_POETRY (153), + + 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; + + QuestTrigger(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/game/quest/handlers/QuestBaseHandler.java b/src/main/java/emu/grasscutter/game/quest/handlers/QuestBaseHandler.java new file mode 100644 index 000000000..5a3514200 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/handlers/QuestBaseHandler.java @@ -0,0 +1,10 @@ +package emu.grasscutter.game.quest.handlers; + +import emu.grasscutter.data.def.QuestData.QuestCondition; +import emu.grasscutter.game.quest.GameQuest; + +public abstract class QuestBaseHandler { + + public abstract boolean execute(GameQuest quest, QuestCondition condition, int... params); + +} diff --git a/src/main/java/emu/grasscutter/server/game/GameServer.java b/src/main/java/emu/grasscutter/server/game/GameServer.java index 71e1cf856..f44aa1bc4 100644 --- a/src/main/java/emu/grasscutter/server/game/GameServer.java +++ b/src/main/java/emu/grasscutter/server/game/GameServer.java @@ -14,6 +14,8 @@ import emu.grasscutter.game.managers.ChatManager; import emu.grasscutter.game.managers.InventoryManager; import emu.grasscutter.game.managers.MultiplayerManager; import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.ServerQuestHandler; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; import emu.grasscutter.game.shop.ShopManager; import emu.grasscutter.game.tower.TowerScheduleManager; import emu.grasscutter.game.world.World; @@ -37,7 +39,8 @@ import static emu.grasscutter.Configuration.*; public final class GameServer extends KcpServer { private final InetSocketAddress address; private final GameServerPacketHandler packetHandler; - + private final ServerQuestHandler questHandler; + private final Map players; private final Set worlds; @@ -68,6 +71,7 @@ public final class GameServer extends KcpServer { this.setServerInitializer(new GameServerInitializer(this)); this.address = address; this.packetHandler = new GameServerPacketHandler(PacketHandler.class); + this.questHandler = new ServerQuestHandler(); this.players = new ConcurrentHashMap<>(); this.worlds = Collections.synchronizedSet(new HashSet<>()); @@ -91,6 +95,10 @@ public final class GameServer extends KcpServer { return packetHandler; } + public ServerQuestHandler getQuestHandler() { + return questHandler; + } + public Map getPlayers() { return players; } 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 309d7e2e2..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,6 +1,7 @@ package emu.grasscutter.server.packet.recv; import emu.grasscutter.game.inventory.GameItem; +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; @@ -14,6 +15,8 @@ public class HandlerNpcTalkReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { NpcTalkReq req = NpcTalkReq.parseFrom(payload); + + 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/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..fa2e8ab81 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java @@ -0,0 +1,34 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.game.player.Player; +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/java/emu/grasscutter/tools/Tools.java b/src/main/java/emu/grasscutter/tools/Tools.java index 4a5af6e49..5b0f563ee 100644 --- a/src/main/java/emu/grasscutter/tools/Tools.java +++ b/src/main/java/emu/grasscutter/tools/Tools.java @@ -19,9 +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.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; @@ -88,7 +90,7 @@ public final class Tools { final class ToolsWithLanguageOption { @SuppressWarnings("deprecation") public static void createGmHandbook(String language) throws Exception { - ResourceLoader.loadResources(); + ResourceLoader.loadAll(); Map map; try (InputStreamReader fileReader = new InputStreamReader(new FileInputStream(Utils.toFilePath(RESOURCE("TextMap/TextMap"+language+".json"))), StandardCharsets.UTF_8)) { @@ -150,6 +152,18 @@ final class ToolsWithLanguageOption { writer.println(); + writer.println("// Quests"); + list = new ArrayList<>(GameData.getQuestDataMap().keySet()); + Collections.sort(list); + + for (Integer id : list) { + 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(); + writer.println("// Monsters"); list = new ArrayList<>(GameData.getMonsterDataMap().keySet()); Collections.sort(list); diff --git a/src/main/resources/languages/en-US.json b/src/main/resources/languages/en-US.json index 42b52d7f5..02945e6d4 100644 --- a/src/main/resources/languages/en-US.json +++ b/src/main/resources/languages/en-US.json @@ -215,6 +215,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.",