diff --git a/src/main/java/emu/grasscutter/data/GameData.java b/src/main/java/emu/grasscutter/data/GameData.java index a7e7a67cb..0a86451d3 100644 --- a/src/main/java/emu/grasscutter/data/GameData.java +++ b/src/main/java/emu/grasscutter/data/GameData.java @@ -32,7 +32,7 @@ public class GameData { // ExcelConfigs private static final Int2ObjectMap playerLevelDataMap = new Int2ObjectOpenHashMap<>(); - + private static final Int2ObjectMap avatarDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap avatarLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap avatarSkillDepotDataMap = new Int2ObjectOpenHashMap<>(); @@ -42,7 +42,7 @@ public class GameData { private static final Int2ObjectMap avatarPromoteDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap avatarTalentDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap proudSkillDataMap = new Int2ObjectOpenHashMap<>(); - + private static final Int2ObjectMap itemDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap reliquaryLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap reliquaryAffixDataMap = new Int2ObjectOpenHashMap<>(); @@ -59,11 +59,11 @@ public class GameData { private static final Int2ObjectMap gadgetDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap monsterCurveDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap monsterDescribeDataMap = new Int2ObjectOpenHashMap<>(); - + private static final Int2ObjectMap avatarFlycloakDataMap = new Int2ObjectLinkedOpenHashMap<>(); private static final Int2ObjectMap avatarCostumeDataMap = new Int2ObjectLinkedOpenHashMap<>(); private static final Int2ObjectMap avatarCostumeDataItemIdMap = new Int2ObjectLinkedOpenHashMap<>(); - + private static final Int2ObjectMap sceneDataMap = new Int2ObjectLinkedOpenHashMap<>(); private static final Int2ObjectMap fetterDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap codexQuestDataMap = new Int2ObjectOpenHashMap<>(); @@ -78,7 +78,7 @@ public class GameData { private static final ArrayList codexReliquaryArrayList = new ArrayList<>(); private static final Int2ObjectMap fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap rewardDataMap = new Int2ObjectOpenHashMap<>(); - + private static final Int2ObjectMap worldAreaDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap worldLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap dailyDungeonDataMap = new Int2ObjectOpenHashMap<>(); @@ -110,6 +110,8 @@ public class GameData { @Getter private static final Int2ObjectMap musicGameBasicDataMap = new Int2ObjectOpenHashMap<>(); @Getter private static final Int2ObjectMap personalLineDataMap = new Int2ObjectOpenHashMap<>(); @Getter private static final Int2ObjectMap chapterDataMap = new Int2ObjectOpenHashMap<>(); + @Getter private static final Int2ObjectMap triggerExcelConfigDataMap = new Int2ObjectOpenHashMap<>(); + @Getter private static final Map scriptSceneDataMap = new HashMap<>(); @Getter private static final Int2ObjectMap openStateDataMap = new Int2ObjectOpenHashMap<>(); @@ -117,29 +119,33 @@ public class GameData { private static Map> fetters = new HashMap<>(); private static Map> shopGoods = new HashMap<>(); private static final IntList scenePointIdList = new IntArrayList(); + + + @Getter private static final List openStateList = new ArrayList<>(); + public static Int2ObjectMap getMapByResourceDef(Class resourceDefinition) { Int2ObjectMap map = null; - + try { Field field = GameData.class.getDeclaredField(Utils.lowerCaseFirstChar(resourceDefinition.getSimpleName()) + "Map"); field.setAccessible(true); - + map = (Int2ObjectMap) field.get(null); - + field.setAccessible(false); } catch (Exception e) { Grasscutter.getLogger().error("Error fetching resource map for " + resourceDefinition.getSimpleName(), e); } - + return map; } public static Int2ObjectMap getAbilityHashes() { return abilityHashes; } - + public static Map getAbilityEmbryoInfo() { return abilityEmbryos; } @@ -155,7 +161,7 @@ public class GameData { public static Map getScenePointEntries() { return scenePointEntries; } - + // TODO optimize public static ScenePointEntry getScenePointEntryById(int sceneId, int pointId) { return getScenePointEntries().get(sceneId + "_" + pointId); @@ -172,15 +178,15 @@ public class GameData { public static Int2ObjectMap getHomeworldDefaultSaveData() { return homeworldDefaultSaveData; } - + public static Int2ObjectMap getSceneNpcBornData() { return npcBornData; } - + public static Int2ObjectMap getAvatarDataMap() { return avatarDataMap; } - + public static Int2ObjectMap getItemDataMap() { return itemDataMap; } @@ -188,7 +194,7 @@ public class GameData { public static Int2ObjectMap getAvatarSkillDepotDataMap() { return avatarSkillDepotDataMap; } - + public static Int2ObjectMap getAvatarSkillDataMap() { return avatarSkillDataMap; } @@ -212,11 +218,11 @@ public class GameData { public static Int2ObjectMap getWeaponLevelDataMap() { return weaponLevelDataMap; } - + public static Int2ObjectMap getReliquaryAffixDataMap() { return reliquaryAffixDataMap; } - + public static Int2ObjectMap getReliquaryMainPropDataMap() { return reliquaryMainPropDataMap; } @@ -228,7 +234,7 @@ public class GameData { public static Int2ObjectMap getWeaponCurveDataMap() { return weaponCurveDataMap; } - + public static Int2ObjectMap getAvatarCurveDataMap() { return avatarCurveDataMap; } @@ -237,11 +243,11 @@ public class GameData { ReliquaryLevelData levelData = reliquaryLevelDataMap.get((rankLevel << 8) + level); return levelData != null ? levelData.getExp() : 0; } - + public static ReliquaryLevelData getRelicLevelData(int rankLevel, int level) { return reliquaryLevelDataMap.get((rankLevel << 8) + level); } - + public static WeaponPromoteData getWeaponPromoteData(int promoteId, int promoteLevel) { return weaponPromoteDataMap.get((promoteId << 8) + promoteLevel); } @@ -257,7 +263,7 @@ public class GameData { return 0; } } - + public static AvatarPromoteData getAvatarPromoteData(int promoteId, int promoteLevel) { return avatarPromoteDataMap.get((promoteId << 8) + promoteLevel); } @@ -271,7 +277,7 @@ public class GameData { AvatarFetterLevelData levelData = avatarFetterLevelDataMap.get(level); return levelData != null ? levelData.getExp() : 0; } - + public static Int2ObjectMap getProudSkillDataMap() { return proudSkillDataMap; } @@ -318,7 +324,7 @@ public class GameData { public static Int2ObjectMap getAvatarCostumeDataMap() { return avatarCostumeDataMap; } - + public static Int2ObjectMap getAvatarCostumeDataItemIdMap() { return avatarCostumeDataItemIdMap; } @@ -359,7 +365,7 @@ public class GameData { public static Int2ObjectMap getWorldAreaDataMap() { return worldAreaDataMap; } - + public static Int2ObjectMap getWorldLevelDataMap() { return worldLevelDataMap; } @@ -367,7 +373,7 @@ public class GameData { public static Int2ObjectMap getDungeonDataMap() { return dungeonDataMap; } - + public static Int2ObjectMap getDailyDungeonDataMap() { return dailyDungeonDataMap; } @@ -403,11 +409,11 @@ public class GameData { public static Int2ObjectMap getTowerFloorDataMap(){ return towerFloorDataMap; } - + public static Int2ObjectMap getTowerLevelDataMap(){ return towerLevelDataMap; } - + public static Int2ObjectMap getTowerScheduleDataMap(){ return towerScheduleDataMap; } @@ -419,19 +425,19 @@ public class GameData { public static Int2ObjectMap getForgeDataMap() { return forgeDataMap; } - + public static Int2ObjectMap getHomeWorldLevelDataMap() { return homeWorldLevelDataMap; } - + public static Int2ObjectMap getFurnitureMakeConfigDataMap() { return furnitureMakeConfigDataMap; } - + public static Int2ObjectMap getGatherDataMap() { return gatherDataMap; } - + public static Int2ObjectMap getInvestigationMonsterDataMap() { return investigationMonsterDataMap; } diff --git a/src/main/java/emu/grasscutter/data/ResourceLoader.java b/src/main/java/emu/grasscutter/data/ResourceLoader.java index b2ddbe5d6..2e9c993af 100644 --- a/src/main/java/emu/grasscutter/data/ResourceLoader.java +++ b/src/main/java/emu/grasscutter/data/ResourceLoader.java @@ -56,22 +56,23 @@ public class ResourceLoader { public static void loadAll() { Grasscutter.getLogger().info(translate("messages.status.resources.loading")); - // Load ability lists - loadAbilityEmbryos(); - loadOpenConfig(); - loadAbilityModifiers(); - // Load resources - loadResources(); - // Process into depots - GameDepot.load(); - // Load spawn data and quests - loadSpawnData(); - loadQuests(); - // Load scene points - must be done AFTER resources are loaded - loadScenePoints(); - // Load default home layout - loadHomeworldDefaultSaveData(); - loadNpcBornData(); + // Load ability lists + loadAbilityEmbryos(); + loadOpenConfig(); + loadAbilityModifiers(); + // Load resources + loadResources(); + // Process into depots + GameDepot.load(); + // Load spawn data and quests + loadSpawnData(); + loadQuests(); + loadScriptSceneData(); + // Load scene points - must be done AFTER resources are loaded + loadScenePoints(); + // Load default home layout + loadHomeworldDefaultSaveData(); + loadNpcBornData(); Grasscutter.getLogger().info(translate("messages.status.resources.finish")); } @@ -432,10 +433,32 @@ public class ResourceLoader { Grasscutter.getLogger().debug("Loaded " + GameData.getMainQuestDataMap().size() + " MainQuestDatas."); } - @SneakyThrows - private static void loadHomeworldDefaultSaveData() { - var folder = Files.list(Path.of(RESOURCE("BinOutput/HomeworldDefaultSave"))).toList(); - var pattern = Pattern.compile("scene(.*)_home_config.json"); + public static void loadScriptSceneData() { + File folder = new File(RESOURCE("ScriptSceneData/")); + + if (!folder.exists()) { + return; + } + + for (File file : folder.listFiles()) { + ScriptSceneData sceneData; + try (FileReader fileReader = new FileReader(file)) { + sceneData = Grasscutter.getGsonFactory().fromJson(fileReader, ScriptSceneData.class); + } catch (Exception e) { + e.printStackTrace(); + continue; + } + + GameData.getScriptSceneDataMap().put(file.getName(), sceneData); + } + + Grasscutter.getLogger().debug("Loaded " + GameData.getScriptSceneDataMap().size() + " ScriptSceneDatas."); + } + + @SneakyThrows + private static void loadHomeworldDefaultSaveData(){ + var folder = Files.list(Path.of(RESOURCE("BinOutput/HomeworldDefaultSave"))).toList(); + var pattern = Pattern.compile("scene(.*)_home_config.json"); for (var file : folder) { var matcher = pattern.matcher(file.getFileName().toString()); diff --git a/src/main/java/emu/grasscutter/data/binout/MainQuestData.java b/src/main/java/emu/grasscutter/data/binout/MainQuestData.java index eda72e8a5..6fa65b975 100644 --- a/src/main/java/emu/grasscutter/data/binout/MainQuestData.java +++ b/src/main/java/emu/grasscutter/data/binout/MainQuestData.java @@ -1,35 +1,41 @@ package emu.grasscutter.data.binout; +import dev.morphia.annotations.Entity; import emu.grasscutter.game.quest.enums.QuestType; import lombok.Data; +import java.util.List; +import java.util.Objects; public class MainQuestData { private int id; + private int ICLLDPJFIMA; private int series; private QuestType type; - + private long titleTextMapHash; private int[] suggestTrackMainQuestList; private int[] rewardIdList; - + private SubQuestData[] subQuests; - - public int getId() { - return id; - } - + private List talks; + private long[] preloadLuaList; + + 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; } @@ -37,14 +43,28 @@ public class MainQuestData { public int[] getRewardIdList() { return rewardIdList; } - + public SubQuestData[] getSubQuests() { return subQuests; } + public List getTalks() { + return talks; + } + + public void onLoad() { + this.talks = talks.stream().filter(Objects::nonNull).toList(); + } @Data public static class SubQuestData { private int subId; private int order; } + + + @Data @Entity + public static class TalkData { + private int id; + private String heroTalk; + } } diff --git a/src/main/java/emu/grasscutter/data/binout/ScriptSceneData.java b/src/main/java/emu/grasscutter/data/binout/ScriptSceneData.java new file mode 100644 index 000000000..5634f61f0 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/binout/ScriptSceneData.java @@ -0,0 +1,23 @@ +package emu.grasscutter.data.binout; + +import com.google.gson.annotations.SerializedName; +import emu.grasscutter.scripts.data.SceneGroup; +import emu.grasscutter.utils.Position; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +public class ScriptSceneData { + Map scriptObjectList; + + @Data + public static class ScriptObject { + //private SceneGroup groups; + @SerializedName("dummy_points") + private Map> dummyPoints; + + + } +} diff --git a/src/main/java/emu/grasscutter/data/excels/QuestData.java b/src/main/java/emu/grasscutter/data/excels/QuestData.java index a0f34fc62..e522fb5b1 100644 --- a/src/main/java/emu/grasscutter/data/excels/QuestData.java +++ b/src/main/java/emu/grasscutter/data/excels/QuestData.java @@ -36,10 +36,14 @@ public class QuestData extends GameResource { private List beginExec; private List finishExec; private List failExec; + private Guide guide; + //ResourceLoader not happy if you remove getId() ~~ public int getId() { return subId; } + //Added getSubId() for clarity + public int getSubId() {return subId;} public int getMainId() { return mainId; @@ -62,7 +66,7 @@ public class QuestData extends GameResource { } public LogicType getAcceptCondComb() { - return acceptCondComb; + return acceptCondComb == null ? LogicType.LOGIC_NONE : acceptCondComb; } public List getAcceptCond() { @@ -70,7 +74,7 @@ public class QuestData extends GameResource { } public LogicType getFinishCondComb() { - return finishCondComb; + return finishCondComb == null ? LogicType.LOGIC_NONE : finishCondComb; } public List getFinishCond() { @@ -78,7 +82,7 @@ public class QuestData extends GameResource { } public LogicType getFailCondComb() { - return failCondComb; + return failCondComb == null ? LogicType.LOGIC_NONE : failCondComb; } public List getFailCond() { @@ -118,4 +122,11 @@ public class QuestData extends GameResource { private String count; } + + @Data + public static class Guide { + private String type; + private List param; + private int guideScene; + } } diff --git a/src/main/java/emu/grasscutter/data/excels/TriggerExcelConfigData.java b/src/main/java/emu/grasscutter/data/excels/TriggerExcelConfigData.java new file mode 100644 index 000000000..e284ab273 --- /dev/null +++ b/src/main/java/emu/grasscutter/data/excels/TriggerExcelConfigData.java @@ -0,0 +1,13 @@ +package emu.grasscutter.data.excels; + +import emu.grasscutter.data.GameResource; +import emu.grasscutter.data.ResourceType; +import lombok.Getter; + +@ResourceType(name = "TriggerExcelConfigData.json") @Getter +public class TriggerExcelConfigData extends GameResource { + @Getter private int id; + private int sceneId; + private int groupId; + private String triggerName; +} diff --git a/src/main/java/emu/grasscutter/game/entity/EntityRegion.java b/src/main/java/emu/grasscutter/game/entity/EntityRegion.java index 20c135fba..32fac1343 100644 --- a/src/main/java/emu/grasscutter/game/entity/EntityRegion.java +++ b/src/main/java/emu/grasscutter/game/entity/EntityRegion.java @@ -15,6 +15,7 @@ import java.util.concurrent.ConcurrentHashMap; public class EntityRegion extends GameEntity{ private final Position position; private boolean hasNewEntities; + private boolean entityLeave; private final Set entities; // Ids of entities inside this region private final SceneRegion metaRegion; @@ -45,10 +46,17 @@ public class EntityRegion extends GameEntity{ hasNewEntities = false; } - public void removeEntity(GameEntity entity) { - this.getEntities().remove(entity.getId()); + public void removeEntity(int entityId) { + this.getEntities().remove(entityId); + this.entityLeave = true; } + public void removeEntity(GameEntity entity) { + this.getEntities().remove(entity.getId()); + this.entityLeave = true; + } + public boolean entityLeave() {return this.entityLeave;} + public void resetEntityLeave() {this.entityLeave = false;} @Override public Int2FloatOpenHashMap getFightProperties() { return null; diff --git a/src/main/java/emu/grasscutter/game/player/Player.java b/src/main/java/emu/grasscutter/game/player/Player.java index 989f99feb..59bc6b5b6 100644 --- a/src/main/java/emu/grasscutter/game/player/Player.java +++ b/src/main/java/emu/grasscutter/game/player/Player.java @@ -14,12 +14,8 @@ import emu.grasscutter.game.activity.ActivityManager; import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.AvatarStorage; import emu.grasscutter.game.battlepass.BattlePassManager; -import emu.grasscutter.game.entity.EntityMonster; -import emu.grasscutter.game.entity.EntityVehicle; +import emu.grasscutter.game.entity.*; import emu.grasscutter.game.home.GameHome; -import emu.grasscutter.game.entity.EntityGadget; -import emu.grasscutter.game.entity.EntityItem; -import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.expedition.ExpeditionInfo; import emu.grasscutter.game.friends.FriendsList; import emu.grasscutter.game.friends.PlayerProfile; @@ -43,6 +39,7 @@ import emu.grasscutter.game.props.ClimateType; import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.WatcherTriggerType; import emu.grasscutter.game.quest.QuestManager; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.shop.ShopLimit; import emu.grasscutter.game.tower.TowerData; import emu.grasscutter.game.tower.TowerManager; @@ -61,6 +58,7 @@ import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture; import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; +import emu.grasscutter.scripts.data.SceneRegion; import emu.grasscutter.server.event.player.PlayerJoinEvent; import emu.grasscutter.server.event.player.PlayerQuitEvent; import emu.grasscutter.server.game.GameServer; @@ -74,6 +72,7 @@ import emu.grasscutter.utils.Utils; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import lombok.Getter; +import lombok.Setter; import static emu.grasscutter.config.Configuration.*; @@ -122,6 +121,7 @@ public class Player { @Getter private Map expeditionInfo; @Getter private Map unlockedRecipies; @Getter private List activeForges; + @Getter private Map questGlobalVariables; @Transient private long nextGuid = 0; @Transient private int peerId; @@ -151,7 +151,7 @@ public class Player { @Getter private transient CookingManager cookingManager; @Getter private transient ActivityManager activityManager; @Getter private transient PlayerBuffManager buffManager; - + // Manager data (Save-able to the database) private PlayerProfile playerProfile; private TeamManager teamManager; @@ -579,10 +579,39 @@ public class Player { return towerData; } - public PlayerGachaInfo getGachaInfo() { - return gachaInfo; + public void setQuestManager(QuestManager questManager) { + this.questManager = questManager; + } + + public void onEnterRegion(SceneRegion region) { + getQuestManager().forEachActiveQuest(quest -> { + if(quest.getTriggers().containsKey("ENTER_REGION_"+ String.valueOf(region.config_id))) { + // If trigger hasn't been fired yet + if(!Boolean.TRUE.equals(quest.getTriggers().put("ENTER_REGION_"+ String.valueOf(region.config_id), true))) { + //getSession().send(new PacketServerCondMeetQuestListUpdateNotify()); + getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_TRIGGER_FIRE, quest.getTriggerData().get("ENTER_REGION_"+ String.valueOf(region.config_id)).getId(),0); + } + } + }); + } + public void onLeaveRegion(SceneRegion region) { + getQuestManager().forEachActiveQuest(quest -> { + if(quest.getTriggers().containsKey("LEAVE_REGION_"+ String.valueOf(region.config_id))) { + // If trigger hasn't been fired yet + if(!Boolean.TRUE.equals(quest.getTriggers().put("LEAVE_REGION_"+ String.valueOf(region.config_id), true))) { + getSession().send(new PacketServerCondMeetQuestListUpdateNotify()); + getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_TRIGGER_FIRE, quest.getTriggerData().get("LEAVE_REGION_"+ String.valueOf(region.config_id)).getId(),0); + } + } + }); + + } + public PlayerGachaInfo getGachaInfo() { + return gachaInfo; + } + public PlayerProfile getProfile() { if (this.playerProfile == null) { this.playerProfile = new PlayerProfile(this); @@ -892,7 +921,7 @@ public class Player { public boolean hasSentLoginPackets() { return hasSentLoginPackets; } - + public void addAvatar(Avatar avatar, boolean addToCurrentTeam) { boolean result = getAvatars().addAvatar(avatar); @@ -1327,6 +1356,11 @@ public class Player { // Execute daily reset logic if this is a new day. this.doDailyReset(); + + // Rewind active quests, and put the player to a rewind position it finds (if any) of an active quest + getQuestManager().onLogin(); + + // Packets session.send(new PacketPlayerDataNotify(this)); // Player data session.send(new PacketStoreWeightLimitNotify()); diff --git a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java index df2a7d826..000dad6db 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java @@ -2,7 +2,15 @@ package emu.grasscutter.game.quest; import java.util.*; +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.binout.ScriptSceneData; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.enums.LogicType; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.scripts.ScriptLoader; import emu.grasscutter.server.packet.send.PacketCodexDataUpdateNotify; +import emu.grasscutter.utils.Position; +import lombok.Getter; import org.bson.types.ObjectId; import dev.morphia.annotations.Entity; @@ -11,6 +19,7 @@ import dev.morphia.annotations.Indexed; import dev.morphia.annotations.Transient; import emu.grasscutter.data.GameData; import emu.grasscutter.data.binout.MainQuestData; +import emu.grasscutter.data.binout.MainQuestData.*; import emu.grasscutter.data.excels.RewardData; import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.player.Player; @@ -23,24 +32,33 @@ 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; + +import javax.script.Bindings; +import javax.script.CompiledScript; +import javax.script.ScriptException; + +import static emu.grasscutter.config.Configuration.SCRIPT; @Entity(value = "quests", useDiscriminator = false) public class GameMainQuest { @Id private ObjectId id; + @Indexed @Getter private int ownerUid; + @Transient @Getter private Player owner; + @Transient @Getter private QuestManager questManager; + @Getter private Map childQuests; + @Getter private int parentQuestId; + @Getter private int[] questVars; + //QuestUpdateQuestVarReq is sent in two stages... + @Getter private List questVarsUpdate; + @Getter private ParentQuestState state; + @Getter private boolean isFinished; + @Getter List questGroupSuites; - @Indexed private int ownerUid; - @Transient private Player owner; - - private Map childQuests; - - private int parentQuestId; - private int[] questVars; - private ParentQuestState state; - private boolean isFinished; - List questGroupSuites; + @Getter int[] suggestTrackMainQuestList; + @Getter private Map talks; + //key is subId + private Map rewindPositions; + private Map rewindRotations; @Deprecated // Morphia only. Do not use. public GameMainQuest() {} @@ -48,52 +66,60 @@ public class GameMainQuest { public GameMainQuest(Player player, int parentQuestId) { this.owner = player; this.ownerUid = player.getUid(); + this.questManager = player.getQuestManager(); this.parentQuestId = parentQuestId; this.childQuests = new HashMap<>(); - this.questVars = new int[5]; + this.talks = new HashMap<>(); + //official server always has a list of 5 questVars, with default value 0 + this.questVars = new int[] {0,0,0,0,0}; this.state = ParentQuestState.PARENT_QUEST_STATE_NONE; this.questGroupSuites = new ArrayList<>(); + this.rewindPositions = new HashMap<>(); + this.rewindRotations = new HashMap<>(); + addAllChildQuests(); + addRewindPoints(); } - public int getParentQuestId() { - return parentQuestId; - } - - public int getOwnerUid() { - return ownerUid; - } - - public Player getOwner() { - return owner; - } + private void addAllChildQuests() { + List subQuestIds = Arrays.stream(GameData.getMainQuestDataMap().get(this.parentQuestId).getSubQuests()).map(SubQuestData::getSubId).toList(); + for (Integer subQuestId : subQuestIds) { + QuestData questConfig = GameData.getQuestDataMap().get(subQuestId); + this.childQuests.put(subQuestId, new GameQuest(this, questConfig)); + } + } public void setOwner(Player player) { if (player.getUid() != this.getOwnerUid()) return; this.owner = player; } - public Map getChildQuests() { - return childQuests; - } + public int getQuestVar(int i) { + return questVars[i]; + } + public void setQuestVar(int i, int value) { + int previousValue = this.questVars[i]; + this.questVars[i] = value; + Grasscutter.getLogger().debug("questVar {} value changed from {} to {}", i, previousValue, value); + } + + public void incQuestVar(int i, int inc) { + int previousValue = this.questVars[i]; + this.questVars[i] += inc; + Grasscutter.getLogger().debug("questVar {} value incremented from {} to {}", i, previousValue, previousValue + inc); + } + + public void decQuestVar(int i, int dec) { + int previousValue = this.questVars[i]; + this.questVars[i] -= dec; + Grasscutter.getLogger().debug("questVar {} value decremented from {} to {}", i, previousValue, previousValue - dec); + } + 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 List getQuestGroupSuites() { - return questGroupSuites; + public GameQuest getChildQuestByOrder(int order) { + return this.getChildQuests().values().stream().filter(p -> p.getQuestData().getOrder() == order).toList().get(0); } public void finish() { @@ -120,9 +146,193 @@ public class GameMainQuest { // handoff main quest if(mainQuestData.getSuggestTrackMainQuestList() != null){ Arrays.stream(mainQuestData.getSuggestTrackMainQuestList()) - .forEach(getOwner().getQuestManager()::startMainQuest); + .forEach(getQuestManager()::startMainQuest); } } + //TODO + public void fail() {} + public void cancel() {} + + // Rewinds to the last finished/unfinished rewind quest, and returns the avatar rewind position (if it exists) + public List rewind() { + if(this.questManager == null) { + this.questManager = getOwner().getQuestManager(); + } + List sortedByOrder = new ArrayList<>(getChildQuests().values().stream().filter(q -> q.getQuestData().isRewind()).toList()); + sortedByOrder.sort((a,b) -> { + if( a == b){ + return 0; + } + return a.getQuestData().getOrder() > b.getQuestData().getOrder() ? 1 : -1;}); + boolean didRewind = false; + for (GameQuest quest : sortedByOrder) { + int i = sortedByOrder.indexOf(quest); + if( i == sortedByOrder.size()) { + didRewind = quest.rewind(null); + } else { + didRewind = quest.rewind(sortedByOrder.get(i+1)); + } + if(didRewind) { + break; + } + } + List rewindQuests = getChildQuests().values().stream() + .filter(p -> (p.getState() == QuestState.QUEST_STATE_UNFINISHED || p.getState() == QuestState.QUEST_STATE_FINISHED) && p.getQuestData().isRewind()).toList(); + for (GameQuest quest : rewindQuests) { + if(rewindPositions.containsKey(quest.getSubQuestId())) { + List posAndRot = new ArrayList<>(); + posAndRot.add(0,rewindPositions.get(quest.getSubQuestId())); + posAndRot.add(1,rewindRotations.get(quest.getSubQuestId())); + return posAndRot; + } + } + return null; + } + public void addRewindPoints() { + Bindings bindings = ScriptLoader.getEngine().createBindings(); + + CompiledScript cs = ScriptLoader.getScriptByPath( + SCRIPT("Quest/Share/Q" + getParentQuestId() + "ShareConfig." + ScriptLoader.getScriptType())); + + if (cs == null) { + Grasscutter.getLogger().error("Couldn't find Q" + getParentQuestId() + "ShareConfig." + ScriptLoader.getScriptType()); + return; + } + + + // Eval script + try { + cs.eval(bindings); + + var rewindDataMap = ScriptLoader.getSerializer().toMap(RewindData.class, bindings.get("rewind_data")); + for(String subId : rewindDataMap.keySet()) { + RewindData questRewind = rewindDataMap.get(subId); + if(questRewind != null) { + RewindData.AvatarData avatarData = questRewind.getAvatar(); + if(avatarData != null) { + String avatarPos = avatarData.getPos(); + QuestData.Guide guide = GameData.getQuestDataMap().get(Integer.valueOf(subId)).getGuide(); + if (guide != null) { + int sceneId = guide.getGuideScene(); + ScriptSceneData fullGlobals = GameData.getScriptSceneDataMap().get("flat.luas.scenes.full_globals.lua.json"); + if(fullGlobals != null) { + ScriptSceneData.ScriptObject dummyPointScript = fullGlobals.getScriptObjectList().get(sceneId + "/scene" + sceneId + "_dummy_points.lua"); + if (dummyPointScript != null) { + Map> dummyPointMap = dummyPointScript.getDummyPoints(); + if (dummyPointMap != null) { + List avatarPosPos = dummyPointMap.get(avatarPos + ".pos"); + if (avatarPosPos != null) { + Position pos = new Position(avatarPosPos.get(0),avatarPosPos.get(1),avatarPosPos.get(2)); + List avatarPosRot = dummyPointMap.get(avatarPos + ".rot"); + Position rot = new Position(avatarPosRot.get(0),avatarPosRot.get(1),avatarPosRot.get(2)); + rewindPositions.put(Integer.valueOf(subId),pos); + rewindRotations.put(Integer.valueOf(subId),rot); + Grasscutter.getLogger().debug("Succesfully loaded rewind position for subQuest {}",subId); + } + } + } + } + } + } + } + } + + } catch (ScriptException e) { + Grasscutter.getLogger().error("An error occurred while loading rewind positions"); + } + } + + public void tryAcceptSubQuests(QuestTrigger condType, String paramStr, int... params) { + try { + List subQuestsWithCond = getChildQuests().values().stream() + .filter(p -> p.getState() == QuestState.QUEST_STATE_UNSTARTED) + .filter(p -> p.getQuestData().getAcceptCond().stream().anyMatch(q -> q.getType() == condType)) + .toList(); + + for (GameQuest subQuestWithCond : subQuestsWithCond) { + List acceptCond = subQuestWithCond.getQuestData().getAcceptCond(); + int[] accept = new int[acceptCond.size()]; + + for (int i = 0; i < subQuestWithCond.getQuestData().getAcceptCond().size(); i++) { + QuestData.QuestCondition condition = acceptCond.get(i); + boolean result = this.getOwner().getServer().getQuestSystem().triggerCondition(subQuestWithCond, condition, paramStr, params); + accept[i] = result ? 1 : 0; + } + + boolean shouldAccept = LogicType.calculate(subQuestWithCond.getQuestData().getAcceptCondComb(), accept); + + if (shouldAccept) { + subQuestWithCond.start(); + getQuestManager().getAddToQuestListUpdateNotify().add(subQuestWithCond); + } + + } + this.save(); + } catch (Exception e) { + Grasscutter.getLogger().error("An error occurred while trying to accept quest.", e); + } + + } + + public void tryFailSubQuests(QuestTrigger condType, String paramStr, int... params) { + try { + List subQuestsWithCond = getChildQuests().values().stream() + .filter(p -> p.getState() == QuestState.QUEST_STATE_UNFINISHED) + .filter(p -> p.getQuestData().getFailCond().stream().anyMatch(q -> q.getType() == condType)) + .toList(); + + for (GameQuest subQuestWithCond : subQuestsWithCond) { + List failCond = subQuestWithCond.getQuestData().getFailCond(); + int[] fail = new int[failCond.size()]; + + for (int i = 0; i < subQuestWithCond.getQuestData().getFailCond().size(); i++) { + QuestData.QuestCondition condition = failCond.get(i); + boolean result = this.getOwner().getServer().getQuestSystem().triggerContent(subQuestWithCond, condition, paramStr, params); + fail[i] = result ? 1 : 0; + } + + boolean shouldFail = LogicType.calculate(subQuestWithCond.getQuestData().getFailCondComb(), fail); + + if (shouldFail) { + subQuestWithCond.fail(); + getQuestManager().getAddToQuestListUpdateNotify().add(subQuestWithCond); + } + } + + } catch (Exception e) { + Grasscutter.getLogger().error("An error occurred while trying to fail quest.", e); + } + } + + public void tryFinishSubQuests(QuestTrigger condType, String paramStr, int... params) { + try { + List subQuestsWithCond = getChildQuests().values().stream() + //There are subQuests with no acceptCond, but can be finished (example: 35104) + .filter(p -> p.getState() == QuestState.QUEST_STATE_UNFINISHED && p.getQuestData().getAcceptCond() != null) + .filter(p -> p.getQuestData().getFinishCond().stream().anyMatch(q -> q.getType() == condType)) + .toList(); + + for (GameQuest subQuestWithCond : subQuestsWithCond) { + List finishCond = subQuestWithCond.getQuestData().getFinishCond(); + int[] finish = new int[finishCond.size()]; + + for (int i = 0; i < subQuestWithCond.getQuestData().getFinishCond().size(); i++) { + QuestData.QuestCondition condition = finishCond.get(i); + boolean result = this.getOwner().getServer().getQuestSystem().triggerContent(subQuestWithCond, condition, paramStr, params); + finish[i] = result ? 1 : 0; + } + + boolean shouldFinish = LogicType.calculate(subQuestWithCond.getQuestData().getFinishCondComb(), finish); + + if (shouldFinish) { + subQuestWithCond.finish(); + getQuestManager().getAddToQuestListUpdateNotify().add(subQuestWithCond); + } + } + } catch (Exception e) { + Grasscutter.getLogger().debug("An error occurred while trying to finish quest.", e); + } + } public void save() { DatabaseHelper.saveQuest(this); @@ -131,25 +341,28 @@ public class GameMainQuest { public ParentQuest toProto() { ParentQuest.Builder proto = ParentQuest.newBuilder() .setParentQuestId(getParentQuestId()) - .setIsFinished(isFinished()) - .setParentQuestState(getState().getValue()) - .setCutsceneEncryptionKey(QuestManager.getQuestKey(parentQuestId)); + .setIsFinished(isFinished()); - for (GameQuest quest : this.getChildQuests().values()) { - ChildQuest childQuest = ChildQuest.newBuilder() - .setQuestId(quest.getQuestId()) - .setState(quest.getState().getValue()) - .build(); - proto.addChildQuestList(childQuest); - } + proto.setParentQuestState(getState().getValue()) + .setCutsceneEncryptionKey(QuestManager.getQuestKey(parentQuestId)); + for (GameQuest quest : this.getChildQuests().values()) { + if (quest.getState() != QuestState.QUEST_STATE_UNSTARTED) { + ChildQuest childQuest = ChildQuest.newBuilder() + .setQuestId(quest.getSubQuestId()) + .setState(quest.getState().getValue()) + .build(); + + proto.addChildQuestList(childQuest); + } + } + + for (int i : getQuestVars()) { + proto.addQuestVar(i); + } - 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 index 7d76c152e..769f94734 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameQuest.java @@ -4,51 +4,84 @@ import dev.morphia.annotations.Entity; import dev.morphia.annotations.Transient; import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; -import emu.grasscutter.data.binout.MainQuestData; -import emu.grasscutter.data.binout.MainQuestData.SubQuestData; import emu.grasscutter.data.excels.ChapterData; import emu.grasscutter.data.excels.QuestData; -import emu.grasscutter.data.excels.QuestData.QuestCondition; +import emu.grasscutter.data.excels.TriggerExcelConfigData; import emu.grasscutter.game.player.Player; -import emu.grasscutter.game.quest.enums.LogicType; import emu.grasscutter.game.quest.enums.QuestState; import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.net.proto.ChapterStateOuterClass; import emu.grasscutter.net.proto.QuestOuterClass.Quest; +import emu.grasscutter.scripts.data.SceneGroup; + import emu.grasscutter.server.packet.send.PacketChapterStateNotify; import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify; import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify; import emu.grasscutter.utils.Utils; +import lombok.Getter; +import lombok.Setter; + +import javax.script.Bindings; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @Entity public class GameQuest { - @Transient private GameMainQuest mainQuest; - @Transient private QuestData questData; + @Transient @Getter @Setter private GameMainQuest mainQuest; + @Transient @Getter private QuestData questData; - private int questId; - private int mainQuestId; + @Getter private int subQuestId; + @Getter private int mainQuestId; + @Getter @Setter private QuestState state; - private int startTime; - private int acceptTime; - private int finishTime; + @Getter @Setter private int startTime; + @Getter @Setter private int acceptTime; + @Getter @Setter private int finishTime; - private int[] finishProgressList; - private int[] failProgressList; + @Getter private int[] finishProgressList; + @Getter private int[] failProgressList; + @Transient @Getter private Map triggerData; + @Getter private Map triggers; + private transient Bindings bindings; - @Deprecated // Morphia only. Do not use. - public GameQuest() {} + @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; + public GameQuest(GameMainQuest mainQuest, QuestData questData) { + this.mainQuest = mainQuest; + this.subQuestId = questData.getId(); + this.mainQuestId = questData.getMainId(); + this.questData = questData; + this.state = QuestState.QUEST_STATE_UNSTARTED; + this.triggerData = new HashMap<>(); + this.triggers = new HashMap<>(); + } + + public void start() { this.acceptTime = Utils.getCurrentSeconds(); this.startTime = this.acceptTime; this.state = QuestState.QUEST_STATE_UNFINISHED; + List triggerCond = questData.getFinishCond().stream() + .filter(p -> p.getType() == QuestTrigger.QUEST_CONTENT_TRIGGER_FIRE).toList(); + if(triggerCond.size() > 0) { + for (QuestData.QuestCondition cond : triggerCond) { + TriggerExcelConfigData newTrigger = GameData.getTriggerExcelConfigDataMap().get(cond.getParam()[0]); + if(newTrigger != null) { + if(this.triggerData == null) { + this.triggerData = new HashMap<>(); + } + triggerData.put(newTrigger.getTriggerName(), newTrigger); + triggers.put(newTrigger.getTriggerName(), false); + SceneGroup group = SceneGroup.of(newTrigger.getGroupId()).load(newTrigger.getSceneId()); + getOwner().getWorld().getSceneById(newTrigger.getSceneId()).loadTriggerFromGroup(group, newTrigger.getTriggerName()); + } + } + } - if (questData.getFinishCond() != null && questData.getAcceptCond().size() != 0) { + if (questData.getFinishCond() != null && questData.getFinishCond().size() != 0) { this.finishProgressList = new int[questData.getFinishCond().size()]; } @@ -56,201 +89,136 @@ public class GameQuest { this.failProgressList = new int[questData.getFailCond().size()]; } - this.mainQuest.getChildQuests().put(this.questId, this); + getQuestData().getBeginExec().forEach(e -> getOwner().getServer().getQuestSystem().triggerExec(this, e, e.getParam())); - this.getData().getBeginExec().forEach(e -> getOwner().getServer().getQuestSystem().triggerExec(this, e, e.getParam())); - this.getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_QUEST_STATE_EQUAL, this.questId, this.state.getValue()); - - if (ChapterData.beginQuestChapterMap.containsKey(questId)) { + if (ChapterData.beginQuestChapterMap.containsKey(subQuestId)){ mainQuest.getOwner().sendPacket(new PacketChapterStateNotify( - ChapterData.beginQuestChapterMap.get(questId).getId(), + ChapterData.beginQuestChapterMap.get(subQuestId).getId(), ChapterStateOuterClass.ChapterState.CHAPTER_STATE_BEGIN )); } - Grasscutter.getLogger().debug("Quest {} is started", questId); + //Some subQuests and talks become active when some other subQuests are unfinished (even from different MainQuests) + this.getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_QUEST_STATE_EQUAL, this.getSubQuestId(), this.getState().getValue(),0,0,0); + this.getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_COND_STATE_EQUAL, this.getSubQuestId(), this.getState().getValue(),0,0,0); + + Grasscutter.getLogger().debug("Quest {} is started", subQuestId); } - 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; - } + public String getTriggerNameById(int id) { + TriggerExcelConfigData trigger = GameData.getTriggerExcelConfigDataMap().get(id); + if(trigger != null) { + String triggerName = trigger.getTriggerName(); + return triggerName; } + //return empty string if can't find trigger + return ""; + } - this.getOwner().getSession().send(new PacketQuestProgressUpdateNotify(this)); - this.getOwner().getSession().send(new PacketQuestListUpdateNotify(this)); + public Player getOwner() { + return this.getMainQuest().getOwner(); + } - if (this.getData().finishParent()) { - // This quest finishes the questline - the main quest will also save the quest to db so we dont have to call save() here - this.getMainQuest().finish(); - } else { - // Try and accept other quests if possible - this.tryAcceptQuestLine(); - this.save(); - } + public void setConfig(QuestData config) { + if (getSubQuestId() != config.getId()) return; + this.questData = config; + } - this.getData().getFinishExec().forEach(e -> getOwner().getServer().getQuestSystem().triggerExec(this, e, e.getParam())); + public void setFinishProgress(int index, int value) { + finishProgressList[index] = value; + } - this.getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_QUEST_STATE_EQUAL, this.questId, this.state.getValue()); + public void setFailProgress(int index, int value) { + failProgressList[index] = value; + } - if (ChapterData.endQuestChapterMap.containsKey(questId)) { + public void finish() { + this.state = QuestState.QUEST_STATE_FINISHED; + this.finishTime = Utils.getCurrentSeconds(); + + if (getFinishProgressList() != null) { + Arrays.fill(getFinishProgressList(), 1); + } + + getOwner().getSession().send(new PacketQuestProgressUpdateNotify(this)); + + + if (getQuestData().finishParent()) { + // This quest finishes the questline - the main quest will also save the quest to db, so we don't have to call save() here + getMainQuest().finish(); + } + + getQuestData().getFinishExec().forEach(e -> getOwner().getServer().getQuestSystem().triggerExec(this, e, e.getParam())); + //Some subQuests have conditions that subQuests are finished (even from different MainQuests) + getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_QUEST_STATE_EQUAL, this.subQuestId, this.state.getValue(),0,0,0); + getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_COND_STATE_EQUAL, this.subQuestId, this.state.getValue(),0,0,0); + + if (ChapterData.endQuestChapterMap.containsKey(subQuestId)){ mainQuest.getOwner().sendPacket(new PacketChapterStateNotify( - ChapterData.endQuestChapterMap.get(questId).getId(), + ChapterData.endQuestChapterMap.get(subQuestId).getId(), ChapterStateOuterClass.ChapterState.CHAPTER_STATE_END )); } - Grasscutter.getLogger().debug("Quest {} is finished", questId); - } + Grasscutter.getLogger().debug("Quest {} is finished", subQuestId); + } - public boolean tryAcceptQuestLine() { - try { - MainQuestData questConfig = GameData.getMainQuestDataMap().get(this.getMainQuestId()); + //TODO + public void fail() { + this.state = QuestState.QUEST_STATE_FAILED; + this.finishTime = Utils.getCurrentSeconds(); - for (SubQuestData subQuest : questConfig.getSubQuests()) { - GameQuest quest = getMainQuest().getChildQuestById(subQuest.getSubId()); - - if (quest == null) { - QuestData questData = GameData.getQuestDataMap().get(subQuest.getSubId()); - - if (questData == null || questData.getAcceptCond() == null - || questData.getAcceptCond().size() == 0) { - continue; - } - - int[] accept = new int[questData.getAcceptCond().size()]; - - // TODO - for (int i = 0; i < questData.getAcceptCond().size(); i++) { - QuestCondition condition = questData.getAcceptCond().get(i); - boolean result = getOwner().getServer().getQuestSystem().triggerCondition(this, condition, - condition.getParamStr(), - 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) { - Grasscutter.getLogger().error("An error occurred while trying to accept quest.", e); + if (getFailProgressList() != null) { + Arrays.fill(getFailProgressList(), 1); } + getOwner().getSession().send(new PacketQuestProgressUpdateNotify(this)); + + getQuestData().getFailExec().forEach(e -> getOwner().getServer().getQuestSystem().triggerExec(this, e, e.getParam())); + //Some subQuests have conditions that subQuests fail (even from different MainQuests) + getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_QUEST_STATE_EQUAL, this.subQuestId, this.state.getValue(),0,0,0); + getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_COND_STATE_EQUAL, this.subQuestId, this.state.getValue(),0,0,0); + + } + // Return true if ParentQuest should rewind to this childQuest + public boolean rewind(GameQuest nextRewind) { + if (questData.isRewind()) { + if(nextRewind == null) {return true;} + // if the next isRewind subQuest is none or unstarted, reset all subQuests with order higher than this one, and restart this quest + if(nextRewind.getState() == QuestState.QUEST_STATE_NONE|| nextRewind.getState() == QuestState.QUEST_STATE_UNSTARTED) { + getMainQuest().getChildQuests().values().stream().filter(p -> p.getQuestData().getOrder() > this.getQuestData().getOrder()).forEach(q -> q.setState(QuestState.QUEST_STATE_UNSTARTED)); + this.start(); + return true; + } + } return false; } + public void save() { + getMainQuest().save(); + } - public void save() { - getMainQuest().save(); - } + public Quest toProto() { + Quest.Builder proto = Quest.newBuilder() + .setQuestId(getSubQuestId()) + .setState(getState().getValue()) + .setParentQuestId(getMainQuestId()) + .setStartTime(getStartTime()) + .setStartGameTime(438) + .setAcceptTime(getAcceptTime()); - 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 (getFinishProgressList() != null) { + for (int i : getFinishProgressList()) { + proto.addFinishProgressList(i); + } + } - if (this.getFinishProgressList() != null) { - for (int i : this.getFinishProgressList()) { - proto.addFinishProgressList(i); - } - } + if (getFailProgressList() != null) { + for (int i : getFailProgressList()) { + proto.addFailProgressList(i); + } + } - if (this.getFailProgressList() != null) { - for (int i : this.getFailProgressList()) { - proto.addFailProgressList(i); - } - } - - return proto.build(); - } + 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 index 525231626..8a62dc022 100644 --- a/src/main/java/emu/grasscutter/game/quest/QuestManager.java +++ b/src/main/java/emu/grasscutter/game/quest/QuestManager.java @@ -1,5 +1,6 @@ package emu.grasscutter.game.quest; +import java.beans.Transient; import java.util.*; import java.util.function.Consumer; import emu.grasscutter.Grasscutter; @@ -15,37 +16,134 @@ 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.*; +import emu.grasscutter.utils.Position; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import jdk.jshell.spi.ExecutionControl; +import lombok.Getter; public class QuestManager extends BasePlayerManager { - private final Int2ObjectMap quests; - public static long getQuestKey(int mainQuestId){ + @Getter private final Player player; + @Getter private Map questGlobalVariables; + + @Getter private final Int2ObjectMap mainQuests; + @Getter private List addToQuestListUpdateNotify; + /* + On SetPlayerBornDataReq, the server sends FinishedParentQuestNotify, with this exact + parentQuestList. Captured on Game version 2.7 + Note: quest 40063 is already set to finished, with childQuest 4006406's state set to 3 + */ + + private static Set newPlayerMainQuests = Set.of(303,318,348,349,350,351,416,500, + 501,502,503,504,505,506,507,508,509,20000,20507,20509,21004,21005,21010,21011,21016,21017, + 21020,21021,21025,40063,70121,70124,70511,71010,71012,71013,71015,71016,71017,71555); + + /* + On SetPlayerBornDataReq, the server sends ServerCondMeetQuestListUpdateNotify, with this exact + addQuestIdList. Captured on Game version 2.7 + Total of 161... + */ + /* + private static Set newPlayerServerCondMeetQuestListUpdateNotify = Set.of(3100101, 7104405, 2201601, + 7100801, 1907002, 7293301, 7193801, 7293401, 7193901, 7091001, 7190501, 7090901, 7190401, 7090801, 7190301, + 7195301, 7294801, 7195201, 7293001, 7094001, 7193501, 7293501, 7194001, 7293701, 7194201, 7194301, 7293801, + 7194901, 7194101, 7195001, 7294501, 7294101, 7194601, 7294301, 7194801, 7091301, 7290301, 2102401, 7216801, + 7190201, 7090701, 7093801, 7193301, 7292801, 7227828, 7093901, 7193401, 7292901, 7093701, 7193201, 7292701, + 7082402, 7093601, 7292601, 7193101, 2102301, 7093501, 7292501, 7193001, 7093401, 7292401, 7192901, 7093301, + 7292301, 7192801, 7294201, 7194701, 2100301, 7093201, 7212402, 7292201, 7192701, 7280001, 7293901, 7194401, + 7093101, 7212302, 7292101, 7192601, 7093001, 7292001, 7192501, 7216001, 7195101, 7294601, 2100900, 7092901, + 7291901, 7192401, 7092801, 7291801, 7192301, 2101501, 7092701, 7291701, 7192201, 7106401, 2100716, 7091801, + 7290801, 7191301, 7293201, 7193701, 7094201, 7294001, 7194501, 2102290, 7227829, 7193601, 7094101, 7091401, + 7290401, 7190901, 7106605, 7291601, 7192101, 7092601, 7291501, 7192001, 7092501, 7291401, 7191901, 7092401, + 7291301, 7191801, 7092301, 7211402, 7291201, 7191701, 7092201, 7291101, 7191601, 7092101, 7291001, 7191501, + 7092001, 7290901, 7191401, 7091901, 7290701, 7191201, 7091701, 7290601, 7191101, 7091601, 7290501, 7191001, + 7091501, 7290201, 7190701, 7091201, 7190601, 7091101, 7190101, 7090601, 7090501, 7090401, 7010701, 7090301, + 7090201, 7010103, 7090101 + ); + + */ + + public static long getQuestKey(int mainQuestId){ QuestEncryptionKey questEncryptionKey = GameData.getMainQuestEncryptionMap().get(mainQuestId); return questEncryptionKey != null ? questEncryptionKey.getEncryptionKey() : 0L; } + public QuestManager(Player player) { - public QuestManager(Player player) { super(player); - this.quests = new Int2ObjectOpenHashMap<>(); + this.player = player; + this.questGlobalVariables = player.getQuestGlobalVariables(); + this.mainQuests = new Int2ObjectOpenHashMap<>(); + this.addToQuestListUpdateNotify = new ArrayList<>(); + } + + public void onNewPlayerCreate() { + + List newQuests = this.addMultMainQuests(newPlayerMainQuests); + //getPlayer().sendPacket(new PacketServerCondMeetQuestListUpdateNotify(newPlayerServerCondMeetQuestListUpdateNotify)); + getPlayer().sendPacket(new PacketFinishedParentQuestUpdateNotify(newQuests)); + + } - public Int2ObjectMap getQuests() { - return quests; + public void onLogin() { + + List activeQuests = getActiveMainQuests(); + for(GameMainQuest quest : activeQuests) { + List rewindPos = quest.rewind(); // + if(rewindPos != null) { + getPlayer().getPosition().set(rewindPos.get(0)); + getPlayer().getRotation().set(rewindPos.get(1)); + } + } } - public GameMainQuest getMainQuestById(int mainQuestId) { - return getQuests().get(mainQuestId); + private List addMultMainQuests(Set mainQuestIds) { + List newQuests = new ArrayList<>(); + for(Integer id : mainQuestIds) { + getMainQuests().put(id.intValue(),new GameMainQuest(this.player, id)); + getMainQuestById(id).save(); + newQuests.add(getMainQuestById(id)); + } + return newQuests; } + /* + Looking through mainQuests 72201-72208 and 72174, we can infer that a questGlobalVar's default value is 0 + */ + public Integer getQuestGlobalVarValue(Integer variable) { + return this.questGlobalVariables.getOrDefault(variable,0); + } + + public void setQuestGlobalVarValue(Integer variable, Integer value) { + Integer previousValue = this.questGlobalVariables.put(variable,value); + Grasscutter.getLogger().debug("Changed questGlobalVar {} value from {} to {}", variable, previousValue==null ? 0: previousValue, value); + } + public void incQuestGlobalVarValue(Integer variable, Integer inc) { + // + Integer previousValue = this.questGlobalVariables.getOrDefault(variable,0); + this.questGlobalVariables.put(variable,previousValue + inc); + Grasscutter.getLogger().debug("Incremented questGlobalVar {} value from {} to {}", variable, previousValue, previousValue + inc); + } + //In MainQuest 998, dec is passed as a positive integer + public void decQuestGlobalVarValue(Integer variable, Integer dec) { + // + Integer previousValue = this.questGlobalVariables.getOrDefault(variable,0); + this.questGlobalVariables.put(variable,previousValue - dec); + Grasscutter.getLogger().debug("Decremented questGlobalVar {} value from {} to {}", variable, previousValue, previousValue - dec); + } + + public GameMainQuest getMainQuestById(int mainQuestId) { + return getMainQuests().get(mainQuestId); + } + public GameQuest getQuestById(int questId) { QuestData questConfig = GameData.getQuestDataMap().get(questId); if (questConfig == null) { return null; } - GameMainQuest mainQuest = getQuests().get(questConfig.getMainId()); + GameMainQuest mainQuest = getMainQuests().get(questConfig.getMainId()); if (mainQuest == null) { return null; @@ -54,34 +152,34 @@ public class QuestManager extends BasePlayerManager { 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 void forEachQuest(Consumer callback) { + for (GameMainQuest mainQuest : getMainQuests().values()) { + for (GameQuest quest : mainQuest.getChildQuests().values()) { + callback.accept(quest); + } + } + } - public void forEachMainQuest(Consumer callback) { - for (GameMainQuest mainQuest : getQuests().values()) { - callback.accept(mainQuest); - } - } + public void forEachMainQuest(Consumer callback) { + for (GameMainQuest mainQuest : getMainQuests().values()) { + callback.accept(mainQuest); + } + } - // 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); - } - } - } - } + // TODO + public void forEachActiveQuest(Consumer callback) { + for (GameMainQuest mainQuest : getMainQuests().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); + public GameMainQuest addMainQuest(QuestData questConfig) { + GameMainQuest mainQuest = new GameMainQuest(getPlayer(), questConfig.getMainId()); + getMainQuests().put(mainQuest.getParentQuestId(), mainQuest); getPlayer().sendPacket(new PacketFinishedParentQuestUpdateNotify(mainQuest)); @@ -105,18 +203,16 @@ public class QuestManager extends BasePlayerManager { // Sub quest GameQuest quest = mainQuest.getChildQuestById(questId); - if (quest != null) { - return null; - } - - // Create - quest = new GameQuest(mainQuest, questConfig); + // Forcefully start + quest.start(); // Save main quest mainQuest.save(); - // Send packet - getPlayer().sendPacket(new PacketQuestListUpdateNotify(quest)); + // Send packet + getPlayer().sendPacket(new PacketQuestListUpdateNotify(mainQuest.getChildQuests().values().stream() + .filter(p -> p.getState() != QuestState.QUEST_STATE_UNSTARTED) + .toList())); return quest; } @@ -136,55 +232,81 @@ public class QuestManager extends BasePlayerManager { triggerEvent(condType, "", params); } - public void triggerEvent(QuestTrigger condType, String paramStr, int... params) { + //TODO + public void triggerEvent(QuestTrigger condType, String paramStr, int... params) { Grasscutter.getLogger().debug("Trigger Event {}, {}, {}", condType, paramStr, params); - Set changedQuests = new HashSet<>(); - - this.forEachActiveQuest(quest -> { - QuestData data = quest.getData(); - - for (int i = 0; i < data.getFinishCond().size(); i++) { - if (quest.getFinishProgressList() == null - || quest.getFinishProgressList().length == 0 - || quest.getFinishProgressList()[i] == 1) { - continue; + List checkMainQuests = this.getMainQuests().values().stream() + .filter(i -> i.getState() != ParentQuestState.PARENT_QUEST_STATE_FINISHED) + .toList(); + switch(condType){ + //accept Conds + case QUEST_COND_STATE_EQUAL: + case QUEST_COND_STATE_NOT_EQUAL: + case QUEST_COND_COMPLETE_TALK: + case QUEST_COND_LUA_NOTIFY: + case QUEST_COND_QUEST_VAR_EQUAL: + case QUEST_COND_QUEST_VAR_GREATER: + case QUEST_COND_QUEST_VAR_LESS: + case QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER: + case QUEST_COND_QUEST_GLOBAL_VAR_EQUAL: + case QUEST_COND_QUEST_GLOBAL_VAR_GREATER: + case QUEST_COND_QUEST_GLOBAL_VAR_LESS: + for (GameMainQuest mainquest : checkMainQuests) { + mainquest.tryAcceptSubQuests(condType, paramStr, params); } + break; - QuestCondition condition = data.getFinishCond().get(i); - - if (condition.getType() != condType) { - continue; + //fail Conds + case QUEST_CONTENT_NOT_FINISH_PLOT: + for (GameMainQuest mainquest : checkMainQuests) { + mainquest.tryFailSubQuests(condType, paramStr, params); } - - boolean result = getPlayer().getServer().getQuestSystem().triggerContent(quest, condition, paramStr, params); - - if (result) { - quest.getFinishProgressList()[i] = 1; - - changedQuests.add(quest); + break; + //finish Conds + case QUEST_CONTENT_COMPLETE_TALK: + case QUEST_CONTENT_FINISH_PLOT: + case QUEST_CONTENT_COMPLETE_ANY_TALK: + case QUEST_CONTENT_LUA_NOTIFY: + case QUEST_CONTENT_QUEST_VAR_EQUAL: + case QUEST_CONTENT_QUEST_VAR_GREATER: + case QUEST_CONTENT_QUEST_VAR_LESS: + case QUEST_CONTENT_ENTER_DUNGEON: + case QUEST_CONTENT_ENTER_ROOM: + case QUEST_CONTENT_INTERACT_GADGET: + case QUEST_CONTENT_TRIGGER_FIRE: + case QUEST_CONTENT_UNLOCK_TRANS_POINT: + for (GameMainQuest mainQuest : checkMainQuests) { + mainQuest.tryFinishSubQuests(condType, paramStr, params); } - } - }); + break; - for (GameQuest quest : changedQuests) { - LogicType logicType = quest.getData().getFailCondComb(); - int[] progress = quest.getFinishProgressList(); + //finish Or Fail Conds + case QUEST_CONTENT_GAME_TIME_TICK: + case QUEST_CONTENT_QUEST_STATE_EQUAL: + case QUEST_CONTENT_ADD_QUEST_PROGRESS: + case QUEST_CONTENT_LEAVE_SCENE: + for (GameMainQuest mainQuest : checkMainQuests) { + mainQuest.tryFailSubQuests(condType, paramStr, params); + mainQuest.tryFinishSubQuests(condType, paramStr, params); + } + break; + //QUEST_EXEC are handled directly by each subQuest - // Handle logical comb - boolean finish = LogicType.calculate(logicType, progress); - - // Finish - if (finish) { - quest.finish(); - } else { - getPlayer().sendPacket(new PacketQuestProgressUpdateNotify(quest)); - quest.save(); - } + //Unused + case QUEST_CONTENT_QUEST_STATE_NOT_EQUAL: + case QUEST_COND_PLAYER_CHOOSE_MALE: + default: + Grasscutter.getLogger().error("Unhandled QuestTrigger {}", condType); } - } + if(this.addToQuestListUpdateNotify.size() != 0){ + this.getPlayer().getSession().send(new PacketQuestListUpdateNotify(this.addToQuestListUpdateNotify)); + this.addToQuestListUpdateNotify.clear(); + } + + } public List getSceneGroupSuite(int sceneId) { - return getQuests().values().stream() + return getMainQuests().values().stream() .filter(i -> i.getState() != ParentQuestState.PARENT_QUEST_STATE_FINISHED) .map(GameMainQuest::getQuestGroupSuites) .filter(Objects::nonNull) @@ -198,12 +320,16 @@ public class QuestManager extends BasePlayerManager { for (GameMainQuest mainQuest : quests) { mainQuest.setOwner(this.getPlayer()); - for (GameQuest quest : mainQuest.getChildQuests().values()) { - quest.setMainQuest(mainQuest); - quest.setConfig(GameData.getQuestDataMap().get(quest.getQuestId())); - } + for (GameQuest quest : mainQuest.getChildQuests().values()) { + quest.setMainQuest(mainQuest); + quest.setConfig(GameData.getQuestDataMap().get(quest.getSubQuestId())); + } - this.getQuests().put(mainQuest.getParentQuestId(), mainQuest); - } + this.getMainQuests().put(mainQuest.getParentQuestId(), mainQuest); + } + } + + public List getActiveMainQuests() { + return getMainQuests().values().stream().filter(p -> !p.isFinished()).toList(); } } diff --git a/src/main/java/emu/grasscutter/game/quest/QuestSystem.java b/src/main/java/emu/grasscutter/game/quest/QuestSystem.java index 16167b010..ad5349cef 100644 --- a/src/main/java/emu/grasscutter/game/quest/QuestSystem.java +++ b/src/main/java/emu/grasscutter/game/quest/QuestSystem.java @@ -65,8 +65,8 @@ public class QuestSystem extends BaseGameSystem { public boolean triggerCondition(GameQuest quest, QuestCondition condition, String paramStr, int... params) { QuestBaseHandler handler = condHandlers.get(condition.getType().getValue()); - if (handler == null || quest.getData() == null) { - Grasscutter.getLogger().debug("Could not trigger condition {} at {}", condition.getType().getValue(), quest.getData()); + if (handler == null || quest.getQuestData() == null) { + Grasscutter.getLogger().debug("Could not trigger condition {} at {}", condition.getType().getValue(), quest.getQuestData()); return false; } @@ -76,8 +76,8 @@ public class QuestSystem extends BaseGameSystem { public boolean triggerContent(GameQuest quest, QuestCondition condition, String paramStr, int... params) { QuestBaseHandler handler = contHandlers.get(condition.getType().getValue()); - if (handler == null || quest.getData() == null) { - Grasscutter.getLogger().debug("Could not trigger content {} at {}", condition.getType().getValue(), quest.getData()); + if (handler == null || quest.getQuestData() == null) { + Grasscutter.getLogger().debug("Could not trigger content {} at {}", condition.getType().getValue(), quest.getQuestData()); return false; } @@ -87,8 +87,8 @@ public class QuestSystem extends BaseGameSystem { public boolean triggerExec(GameQuest quest, QuestExecParam execParam, String... params) { QuestExecHandler handler = execHandlers.get(execParam.getType().getValue()); - if (handler == null || quest.getData() == null) { - Grasscutter.getLogger().debug("Could not trigger exec {} at {}", execParam.getType().getValue(), quest.getData()); + if (handler == null || quest.getQuestData() == null) { + Grasscutter.getLogger().debug("Could not trigger exec {} at {}", execParam.getType().getValue(), quest.getQuestData()); return false; } diff --git a/src/main/java/emu/grasscutter/game/quest/RewindData.java b/src/main/java/emu/grasscutter/game/quest/RewindData.java new file mode 100644 index 000000000..48267a771 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/RewindData.java @@ -0,0 +1,28 @@ +package emu.grasscutter.game.quest; + +import lombok.Data; +import lombok.Getter; + +import java.util.List; +@Data +public class RewindData { + AvatarData avatar; + List npcs; + + @Data + public static class AvatarData { + @Getter private String pos; + } + + @Data + private static class Npc { + private String script; + private int room_id; + private int data_index; + private int id; + private String pos; + private int scene_id; + private String alias; + } +} + diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionCompleteTalk.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionCompleteTalk.java new file mode 100644 index 000000000..6439fec7d --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionCompleteTalk.java @@ -0,0 +1,27 @@ +package emu.grasscutter.game.quest.conditions; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.binout.MainQuestData; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_COND_COMPLETE_TALK) +public class ConditionCompleteTalk extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + GameMainQuest checkMainQuest = quest.getOwner().getQuestManager().getMainQuestById(condition.getParam()[0]/100); + if (checkMainQuest == null || GameData.getMainQuestDataMap().get(checkMainQuest.getParentQuestId()).getTalks() == null) { + Grasscutter.getLogger().debug("Warning: mainQuest {} hasn't been started yet, or has no talks", condition.getParam()[0]/100); + return false; + } + MainQuestData.TalkData talkData = checkMainQuest.getTalks().get(Integer.valueOf(params[0])); + return talkData != null || checkMainQuest.getChildQuestById(params[0]) != null; + } + +} diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionLuaNotify.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionLuaNotify.java index 8d3fd541b..0501d5dbd 100644 --- a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionLuaNotify.java +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionLuaNotify.java @@ -8,7 +8,7 @@ import emu.grasscutter.game.quest.handlers.QuestBaseHandler; @QuestValue(QuestTrigger.QUEST_COND_LUA_NOTIFY) public class ConditionLuaNotify extends QuestBaseHandler { - + //Wrong implementation. Example: 7010226 has no paramStr @Override public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) { return condition.getParam()[0] == Integer.parseInt(paramStr); diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestGlobalVarEqual.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestGlobalVarEqual.java new file mode 100644 index 000000000..94c565333 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestGlobalVarEqual.java @@ -0,0 +1,20 @@ +package emu.grasscutter.game.quest.conditions; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_COND_QUEST_GLOBAL_VAR_EQUAL) +public class ConditionQuestGlobalVarEqual extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + Integer questGlobalVarValue = quest.getMainQuest().getQuestManager().getQuestGlobalVarValue(Integer.valueOf(params[0])); + Grasscutter.getLogger().debug("questGlobarVar {} : {}", params[0],questGlobalVarValue); + return questGlobalVarValue.intValue() == params[1]; + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestGlobalVarGreater.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestGlobalVarGreater.java new file mode 100644 index 000000000..cdd5a3c24 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestGlobalVarGreater.java @@ -0,0 +1,20 @@ +package emu.grasscutter.game.quest.conditions; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_COND_QUEST_GLOBAL_VAR_GREATER) +public class ConditionQuestGlobalVarGreater extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + Integer questGlobalVarValue = quest.getMainQuest().getQuestManager().getQuestGlobalVarValue(Integer.valueOf(params[0])); + Grasscutter.getLogger().debug("questGlobarVar {} : {}", params[0],questGlobalVarValue); + return questGlobalVarValue.intValue() > params[1]; + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestGlobalVarLess.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestGlobalVarLess.java new file mode 100644 index 000000000..826598d45 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestGlobalVarLess.java @@ -0,0 +1,20 @@ +package emu.grasscutter.game.quest.conditions; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_COND_QUEST_GLOBAL_VAR_LESS) +public class ConditionQuestGlobalVarLess extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + Integer questGlobalVarValue = quest.getMainQuest().getQuestManager().getQuestGlobalVarValue(Integer.valueOf(params[0])); + Grasscutter.getLogger().debug("questGlobarVar {} : {}", params[0],questGlobalVarValue); + return questGlobalVarValue.intValue() < params[1]; + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestVarEqual.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestVarEqual.java new file mode 100644 index 000000000..90e7b0e27 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestVarEqual.java @@ -0,0 +1,20 @@ +package emu.grasscutter.game.quest.conditions; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_COND_QUEST_VAR_EQUAL) +public class ConditionQuestVarEqual extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + int questVarValue = quest.getMainQuest().getQuestVars()[params[0]]; + Grasscutter.getLogger().debug("questVar {} : {}", params[0],questVarValue); + return questVarValue == params[1]; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestVarGreater.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestVarGreater.java new file mode 100644 index 000000000..f4a55a58c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestVarGreater.java @@ -0,0 +1,20 @@ +package emu.grasscutter.game.quest.conditions; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_COND_QUEST_VAR_GREATER) +public class ConditionQuestVarGreater extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + int questVarValue = quest.getMainQuest().getQuestVars()[params[0]]; + Grasscutter.getLogger().debug("questVar {} : {}", params[0],questVarValue); + return questVarValue > params[1]; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestVarLess.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestVarLess.java new file mode 100644 index 000000000..5993d586a --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionQuestVarLess.java @@ -0,0 +1,20 @@ +package emu.grasscutter.game.quest.conditions; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_COND_QUEST_VAR_LESS) +public class ConditionQuestVarLess extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + int questVarValue = quest.getMainQuest().getQuestVars()[params[0]]; + Grasscutter.getLogger().debug("questVar {} : {}", params[0],questVarValue); + return questVarValue < params[1]; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java index c993eebb7..f4eb1ed1a 100644 --- a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateEqual.java @@ -1,5 +1,6 @@ package emu.grasscutter.game.quest.conditions; +import emu.grasscutter.Grasscutter; import emu.grasscutter.game.quest.QuestValue; import emu.grasscutter.data.excels.QuestData.QuestCondition; import emu.grasscutter.game.quest.GameQuest; @@ -11,13 +12,16 @@ public class ConditionStateEqual extends QuestBaseHandler { @Override public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) { - GameQuest checkQuest = quest.getOwner().getQuestManager().getQuestById(params[0]); + GameQuest checkQuest = quest.getOwner().getQuestManager().getQuestById(condition.getParam()[0]); + if (checkQuest == null) { + /* + Will spam the console + //Grasscutter.getLogger().debug("Warning: quest {} hasn't been started yet!", condition.getParam()[0]); - if (checkQuest != null) { - return checkQuest.getState().getValue() == params[1]; - } - - return false; + */ + return false; + } + return checkQuest.getState().getValue() == condition.getParam()[1]; } } diff --git a/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateNotEqual.java b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateNotEqual.java new file mode 100644 index 000000000..21c7f5c99 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/conditions/ConditionStateNotEqual.java @@ -0,0 +1,27 @@ +package emu.grasscutter.game.quest.conditions; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_COND_STATE_NOT_EQUAL) +public class ConditionStateNotEqual extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + GameQuest checkQuest = quest.getOwner().getQuestManager().getQuestById(condition.getParam()[0]); + if (checkQuest == null) { + /* + Will spam the console + //Grasscutter.getLogger().debug("Warning: quest {} hasn't been started yet!", condition.getParam()[0]); + + */ + return false; + } + return checkQuest.getState().getValue() != condition.getParam()[1]; + } + +} diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentAddQuestProgress.java b/src/main/java/emu/grasscutter/game/quest/content/ContentAddQuestProgress.java index a49d067f4..ac7685447 100644 --- a/src/main/java/emu/grasscutter/game/quest/content/ContentAddQuestProgress.java +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentAddQuestProgress.java @@ -11,7 +11,12 @@ public class ContentAddQuestProgress extends QuestBaseHandler { @Override public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) { - return condition.getParam()[0] == params[0]; + /* + //paramStr is a lua group, params[0] may also be a lua group! + questid = xxxxxx lua group = xxxxxxyy + count seems relevant only for lua group + */ + return condition.getParam()[0] == params[0]; //missing params[1], paramStr, and count } } diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteAnyTalk.java b/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteAnyTalk.java new file mode 100644 index 000000000..494076c38 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteAnyTalk.java @@ -0,0 +1,22 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.data.binout.MainQuestData; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_CONTENT_COMPLETE_ANY_TALK) +public class ContentCompleteAnyTalk extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + GameMainQuest checkMainQuest = quest.getOwner().getQuestManager().getMainQuestById(params[0]/100); + if (checkMainQuest == null) {return false;} + MainQuestData.TalkData talkData = checkMainQuest.getTalks().get(Integer.valueOf(paramStr)); + return talkData == null || condition.getParamStr().contains(paramStr) || checkMainQuest.getChildQuestById(params[0]) != null; + } + +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java b/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java index 76819f47c..c87640d8c 100644 --- a/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentCompleteTalk.java @@ -1,8 +1,10 @@ package emu.grasscutter.game.quest.content; +import emu.grasscutter.data.binout.MainQuestData; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; import emu.grasscutter.game.quest.QuestValue; import emu.grasscutter.data.excels.QuestData.QuestCondition; -import emu.grasscutter.game.quest.GameQuest; import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.quest.handlers.QuestBaseHandler; @@ -11,7 +13,9 @@ public class ContentCompleteTalk extends QuestBaseHandler { @Override public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) { - return condition.getParam()[0] == params[0]; + GameMainQuest checkMainQuest = quest.getOwner().getQuestManager().getMainQuestById(params[0]/100); + if (checkMainQuest == null) {return false;} + MainQuestData.TalkData talkData = checkMainQuest.getTalks().get(Integer.valueOf(params[0])); + return talkData == null || condition.getParamStr().contains(paramStr) || checkMainQuest.getChildQuestById(params[0]) != null; } - } diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java b/src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java index 4dc291606..ebff4cc27 100644 --- a/src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentEnterDungeon.java @@ -11,7 +11,7 @@ public class ContentEnterDungeon extends QuestBaseHandler { @Override public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) { - return condition.getParam()[0] == params[0]; + return condition.getParam()[0] == params[0]; //missing params[1] } } diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java b/src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java index f3040b18c..fb539cec3 100644 --- a/src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentFinishPlot.java @@ -1,5 +1,7 @@ package emu.grasscutter.game.quest.content; +import emu.grasscutter.data.binout.MainQuestData; +import emu.grasscutter.game.quest.GameMainQuest; import emu.grasscutter.game.quest.QuestValue; import emu.grasscutter.data.excels.QuestData.QuestCondition; import emu.grasscutter.game.quest.GameQuest; @@ -11,7 +13,9 @@ public class ContentFinishPlot extends QuestBaseHandler { @Override public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) { - return condition.getParam()[0] == params[0]; - } + MainQuestData.TalkData talkData = quest.getMainQuest().getTalks().get(Integer.valueOf(params[0])); + GameQuest subQuest = quest.getMainQuest().getChildQuestById(params[0]); + return talkData != null || subQuest != null; + } } diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentLeaveScene.java b/src/main/java/emu/grasscutter/game/quest/content/ContentLeaveScene.java new file mode 100644 index 000000000..dc3b9698c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentLeaveScene.java @@ -0,0 +1,18 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.data.excels.QuestData.QuestCondition; +import emu.grasscutter.game.player.Player; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_CONTENT_LEAVE_SCENE) +public class ContentLeaveScene extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) { + return quest.getOwner().getScene().getPrevScene() == params[0]; + } + +} diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentNotFinishPlot.java b/src/main/java/emu/grasscutter/game/quest/content/ContentNotFinishPlot.java new file mode 100644 index 000000000..f48c4945e --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentNotFinishPlot.java @@ -0,0 +1,22 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.data.binout.MainQuestData; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_CONTENT_NOT_FINISH_PLOT) +public class ContentNotFinishPlot extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + GameMainQuest checkMainQuest = quest.getOwner().getQuestManager().getMainQuestById(params[0]/100); + if (checkMainQuest == null) {return false;} + MainQuestData.TalkData talkData = checkMainQuest.getTalks().get(Integer.valueOf(params[0])); + return talkData == null; + } + +} diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentQuestStateEqual.java b/src/main/java/emu/grasscutter/game/quest/content/ContentQuestStateEqual.java index 511e7e19a..9e862d585 100644 --- a/src/main/java/emu/grasscutter/game/quest/content/ContentQuestStateEqual.java +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentQuestStateEqual.java @@ -11,13 +11,9 @@ public class ContentQuestStateEqual extends QuestBaseHandler { @Override public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) { - GameQuest checkQuest = quest.getOwner().getQuestManager().getQuestById(params[0]); - - if (checkQuest != null) { - return checkQuest.getState().getValue() == params[1]; - } - - return false; + GameQuest checkQuest = quest.getOwner().getQuestManager().getQuestById(condition.getParam()[0]); + if (checkQuest == null) {return false;} + return checkQuest.getState().getValue() == params[1]; } } diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentQuestStateNotEqual.java b/src/main/java/emu/grasscutter/game/quest/content/ContentQuestStateNotEqual.java new file mode 100644 index 000000000..85ff204ab --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentQuestStateNotEqual.java @@ -0,0 +1,23 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_CONTENT_QUEST_STATE_NOT_EQUAL) +public class ContentQuestStateNotEqual extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + GameQuest checkQuest = quest.getOwner().getQuestManager().getQuestById(params[0]); + + if (checkQuest != null) { + return checkQuest.getState().getValue() != params[1]; + } + + return false; + } + +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentQuestVarEqual.java b/src/main/java/emu/grasscutter/game/quest/content/ContentQuestVarEqual.java new file mode 100644 index 000000000..f56de74ab --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentQuestVarEqual.java @@ -0,0 +1,19 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_CONTENT_QUEST_VAR_EQUAL) +public class ContentQuestVarEqual extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + int questVarValue = quest.getMainQuest().getQuestVars()[params[0]]; + Grasscutter.getLogger().debug("questVar {} : {}", params[0],questVarValue); + return questVarValue == params[1]; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentQuestVarGreater.java b/src/main/java/emu/grasscutter/game/quest/content/ContentQuestVarGreater.java new file mode 100644 index 000000000..7eafa3bfa --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentQuestVarGreater.java @@ -0,0 +1,19 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_CONTENT_QUEST_VAR_GREATER) +public class ContentQuestVarGreater extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + int questVarValue = quest.getMainQuest().getQuestVars()[params[0]]; + Grasscutter.getLogger().debug("questVar {} : {}", params[0],questVarValue); + return questVarValue > params[1]; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentQuestVarLess.java b/src/main/java/emu/grasscutter/game/quest/content/ContentQuestVarLess.java new file mode 100644 index 000000000..6994eeb99 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentQuestVarLess.java @@ -0,0 +1,19 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_CONTENT_QUEST_VAR_LESS) +public class ContentQuestVarLess extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + int questVarValue = quest.getMainQuest().getQuestVars()[params[0]]; + Grasscutter.getLogger().debug("questVar {} : {}", params[0],questVarValue); + return questVarValue < params[1]; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentTriggerFire.java b/src/main/java/emu/grasscutter/game/quest/content/ContentTriggerFire.java new file mode 100644 index 000000000..a2eb784e9 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentTriggerFire.java @@ -0,0 +1,25 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.Grasscutter; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.data.excels.TriggerExcelConfigData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_CONTENT_TRIGGER_FIRE) +public class ContentTriggerFire extends QuestBaseHandler { + + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + if(quest.getTriggers().containsKey(quest.getTriggerNameById(params[0]))) { + //We don't want to put a new key here + return quest.getTriggers().get(quest.getTriggerNameById(params[0])); + } else { + Grasscutter.getLogger().error("quest {} doesn't have trigger {}", quest.getSubQuestId(), params[0]); + return false; + } + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentUnlockTransPoint.java b/src/main/java/emu/grasscutter/game/quest/content/ContentUnlockTransPoint.java new file mode 100644 index 000000000..5ae674c55 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentUnlockTransPoint.java @@ -0,0 +1,15 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestBaseHandler; + +@QuestValue(QuestTrigger.QUEST_CONTENT_UNLOCK_TRANS_POINT) +public class ContentUnlockTransPoint extends QuestBaseHandler { + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/enums/QuestState.java b/src/main/java/emu/grasscutter/game/quest/enums/QuestState.java index d258a2582..9d3369414 100644 --- a/src/main/java/emu/grasscutter/game/quest/enums/QuestState.java +++ b/src/main/java/emu/grasscutter/game/quest/enums/QuestState.java @@ -5,10 +5,17 @@ public enum QuestState { QUEST_STATE_UNSTARTED (1), QUEST_STATE_UNFINISHED (2), QUEST_STATE_FINISHED (3), - QUEST_STATE_FAILED (4); - + QUEST_STATE_FAILED (4), + + // Used by lua + NONE (0), + UNSTARTED(1), + UNFINISHED(2), + FINISHED(3), + FAILED(4); + private final int value; - + QuestState(int id) { this.value = id; } diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecDecQuestGlobalVar.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecDecQuestGlobalVar.java new file mode 100644 index 000000000..56a62106c --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecDecQuestGlobalVar.java @@ -0,0 +1,17 @@ +package emu.grasscutter.game.quest.exec; + + +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestExecHandler; + +@QuestValue(QuestTrigger.QUEST_EXEC_DEC_QUEST_GLOBAL_VAR) +public class ExecDecQuestGlobalVar extends QuestExecHandler { + @Override + public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { + quest.getOwner().getQuestManager().decQuestGlobalVarValue(Integer.valueOf(paramStr[0]),Integer.valueOf(paramStr[1])); + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecDecQuestVar.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecDecQuestVar.java new file mode 100644 index 000000000..7cfd54d22 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecDecQuestVar.java @@ -0,0 +1,16 @@ +package emu.grasscutter.game.quest.exec; + +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestExecHandler; + +@QuestValue(QuestTrigger.QUEST_EXEC_DEC_QUEST_VAR) +public class ExecDecQuestVar extends QuestExecHandler { + @Override + public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { + quest.getMainQuest().decQuestVar(Integer.parseInt(paramStr[0]), Integer.parseInt(paramStr[1])); + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecIncQuestGlobalVar.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecIncQuestGlobalVar.java new file mode 100644 index 000000000..327248750 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecIncQuestGlobalVar.java @@ -0,0 +1,16 @@ +package emu.grasscutter.game.quest.exec; + +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestExecHandler; + +@QuestValue(QuestTrigger.QUEST_EXEC_INC_QUEST_GLOBAL_VAR) +public class ExecIncQuestGlobalVar extends QuestExecHandler { + @Override + public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { + quest.getOwner().getQuestManager().incQuestGlobalVarValue(Integer.valueOf(paramStr[0]),Integer.valueOf(paramStr[1])); + return true; + } +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecIncQuestVar.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecIncQuestVar.java new file mode 100644 index 000000000..3919b1e38 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecIncQuestVar.java @@ -0,0 +1,16 @@ +package emu.grasscutter.game.quest.exec; + +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestExecHandler; + +@QuestValue(QuestTrigger.QUEST_EXEC_INC_QUEST_VAR) +public class ExecIncQuestVar extends QuestExecHandler { + @Override + public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { + quest.getMainQuest().incQuestVar(Integer.parseInt(paramStr[0]), Integer.parseInt(paramStr[1])); + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecSetQuestGlobalVar.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecSetQuestGlobalVar.java new file mode 100644 index 000000000..2142f4cff --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecSetQuestGlobalVar.java @@ -0,0 +1,16 @@ +package emu.grasscutter.game.quest.exec; + +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestExecHandler; + +@QuestValue(QuestTrigger.QUEST_EXEC_SET_QUEST_GLOBAL_VAR) +public class ExecSetQuestGlobalVar extends QuestExecHandler { + @Override + public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { + quest.getOwner().getQuestManager().setQuestGlobalVarValue(Integer.valueOf(paramStr[0]),Integer.valueOf(paramStr[1])); + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/quest/exec/ExecSetQuestVar.java b/src/main/java/emu/grasscutter/game/quest/exec/ExecSetQuestVar.java new file mode 100644 index 000000000..6219d913f --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/exec/ExecSetQuestVar.java @@ -0,0 +1,16 @@ +package emu.grasscutter.game.quest.exec; + +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; +import emu.grasscutter.game.quest.handlers.QuestExecHandler; + +@QuestValue(QuestTrigger.QUEST_EXEC_SET_QUEST_VAR) +public class ExecSetQuestVar extends QuestExecHandler { + @Override + public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) { + quest.getMainQuest().setQuestVar(Integer.parseInt(paramStr[0]), Integer.parseInt(paramStr[1])); + return true; + } +} diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index 2646edf87..da6a5965c 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -627,8 +627,14 @@ public class Scene { .flatMap(Collection::stream) .toList(); - onLoadGroup(groups); - Grasscutter.getLogger().info("Scene {} Block {} loaded.", this.getId(), block.id); + onLoadGroup(groups); + Grasscutter.getLogger().info("Scene {} Block {} loaded.", this.getId(), block.id); + } + public void loadTriggerFromGroup(SceneGroup group, String triggerName) { + //Load triggers and regions + getScriptManager().registerTrigger(group.triggers.values().stream().filter(p -> p.name.contains(triggerName)).toList()); + group.regions.values().stream().filter(q -> q.config_id == Integer.parseInt(triggerName.substring(13))).map(region -> new EntityRegion(this, region)) + .forEach(getScriptManager()::registerRegion); } public void onLoadGroup(List groups) { diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index fa05a21a7..38991551b 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -92,6 +92,7 @@ public class SceneScriptManager { } public void registerTrigger(SceneTrigger trigger) { getTriggersByEvent(trigger.event).add(trigger); + Grasscutter.getLogger().debug("Registered trigger {}", trigger.name); } public void deregisterTrigger(List triggers) { triggers.forEach(this::deregisterTrigger); @@ -122,6 +123,7 @@ public class SceneScriptManager { public void registerRegion(EntityRegion region) { regions.put(region.getId(), region); + Grasscutter.getLogger().debug("Registered region {} from group {}", region.getMetaRegion().config_id, region.getGroupId()); } public void registerRegionInGroupSuite(SceneGroup group, SceneSuite suite){ suite.sceneRegions.stream().map(region -> new EntityRegion(this.getScene(), region)) @@ -195,9 +197,30 @@ public class SceneScriptManager { .filter(e -> e.getEntityType() == EntityType.Avatar.getValue() && region.getMetaRegion().contains(e.getPosition())) .forEach(region::addEntity); + var players = region.getScene().getPlayers(); + int targetID = 0; + if(players.size() > 0) + targetID = players.get(0).getUid(); + if (region.hasNewEntities()) { + Grasscutter.getLogger().trace("Call EVENT_ENTER_REGION_{}",region.getMetaRegion().config_id); callEvent(EventType.EVENT_ENTER_REGION, new ScriptArgs(region.getConfigId()) .setSourceEntityId(region.getId()) + .setTargetEntityId(targetID) + ); + + region.resetNewEntities(); + } + + for(int entityId : region.getEntities()) { + if(!region.getMetaRegion().contains(getScene().getEntityById(entityId).getPosition())) { + region.removeEntity(entityId); + + } + } + if (region.entityLeave()) { + callEvent(EventType.EVENT_LEAVE_REGION, new ScriptArgs(region.getConfigId()) + .setSourceEntityId(region.getId()) .setTargetEntityId(region.getFirstEntityId()) ); @@ -286,27 +309,41 @@ public class SceneScriptManager { } private void realCallEvent(int eventType, ScriptArgs params) { - try{ - ScriptLoader.getScriptLib().setSceneScriptManager(this); - for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) { - try{ - ScriptLoader.getScriptLib().setCurrentGroup(trigger.currentGroup); - - LuaValue ret = callScriptFunc(trigger.condition, trigger.currentGroup, params); - Grasscutter.getLogger().trace("Call Condition Trigger {}", trigger.condition); - - if (ret.isboolean() && ret.checkboolean()) { - // the SetGroupVariableValueByGroup in tower need the param to record the first stage time - callScriptFunc(trigger.action, trigger.currentGroup, params); - Grasscutter.getLogger().trace("Call Action Trigger {}", trigger.action); - } - //TODO some ret may not bool - - }finally { - ScriptLoader.getScriptLib().removeCurrentGroup(); - } - } - }finally { + try { + Set relevantTriggers = new HashSet<>(); + if(eventType == EventType.EVENT_ENTER_REGION || eventType == EventType.EVENT_LEAVE_REGION) { + List relevantTriggersList = this.getTriggersByEvent(eventType).stream() + .filter(p -> p.condition.contains(String.valueOf(params.param1))).toList(); + relevantTriggers = new HashSet<>(relevantTriggersList); + } else {relevantTriggers = this.getTriggersByEvent(eventType);} + for (SceneTrigger trigger : relevantTriggers) { + try { + ScriptLoader.getScriptLib().setCurrentGroup(trigger.currentGroup); + LuaValue ret = this.callScriptFunc(trigger.condition, trigger.currentGroup, params); + Grasscutter.getLogger().trace("Call Condition Trigger {}, [{},{},{}]", trigger.condition, params.param1, params.source_eid, params.target_eid); + if (ret.isboolean() && ret.checkboolean()) { + // the SetGroupVariableValueByGroup in tower need the param to record the first stage time + this.callScriptFunc(trigger.action, trigger.currentGroup, params); + Grasscutter.getLogger().trace("Call Action Trigger {}", trigger.action); + if (trigger.event == EventType.EVENT_ENTER_REGION) { + EntityRegion region = this.regions.values().stream().filter(p -> p.getConfigId() == params.param1).toList().get(0); + getScene().getPlayers().forEach(p -> p.onEnterRegion(region.getMetaRegion())); + deregisterRegion(region.getMetaRegion()); + } else if (trigger.event == EventType.EVENT_LEAVE_REGION) { + EntityRegion region = this.regions.values().stream().filter(p -> p.getConfigId() == params.param1).toList().get(0); + getScene().getPlayers().forEach(p -> p.onLeaveRegion(region.getMetaRegion())); + deregisterRegion(region.getMetaRegion()); + } + deregisterTrigger(trigger); + } else { + Grasscutter.getLogger().debug("Condition Trigger {} returned {}", trigger.condition, ret); + } + //TODO some ret do not bool + }finally { + ScriptLoader.getScriptLib().removeCurrentGroup(); + } + } + }finally { // make sure it is removed ScriptLoader.getScriptLib().removeSceneScriptManager(); } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java b/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java index ecba125eb..6266f6a2c 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java @@ -26,7 +26,8 @@ public class SceneRegion { var x = Math.pow(pos.getX() - position.getX(), 2); var y = Math.pow(pos.getY() - position.getY(), 2); var z = Math.pow(pos.getZ() - position.getZ(), 2); - return x + y + z <= (radius ^ 2); + // ^ means XOR in java! + return x + y + z <= (radius*radius); } return false; } diff --git a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java index 6a08bfd04..f8a3ae754 100644 --- a/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java +++ b/src/main/java/emu/grasscutter/scripts/serializer/LuaSerializer.java @@ -29,22 +29,70 @@ public class LuaSerializer implements Serializer { public T toObject(Class type, Object obj) { return serialize(type, (LuaTable) obj); } - - public List serializeList(Class type, LuaTable table) { + + @Override + public Map toMap(Class type, Object obj) { + return serializeMap(type, (LuaTable) obj); + } + + private Map serializeMap(Class type, LuaTable table) { + Map map = new HashMap<>(); + + if (table == null) { + return map; + } + + try { + LuaValue[] keys = table.keys(); + for (LuaValue k : keys) { + try { + LuaValue keyValue = table.get(k); + + T object = null; + + if (keyValue.istable()) { + object = serialize(type, keyValue.checktable()); + } else if (keyValue.isint()) { + object = (T) (Integer) keyValue.toint(); + } else if (keyValue.isnumber()) { + object = (T) (Float) keyValue.tofloat(); // terrible... + } else if (keyValue.isstring()) { + object = (T) keyValue.tojstring(); + } else if (keyValue.isboolean()) { + object = (T) (Boolean) keyValue.toboolean(); + } else { + object = (T) keyValue; + } + + if (object != null) { + map.put(String.valueOf(k),object); + } + } catch (Exception ex) { + + } + } + } catch (Exception e) { + e.printStackTrace(); + } + + return map; + } + + public List serializeList(Class type, LuaTable table) { List list = new ArrayList<>(); - + if (table == null) { return list; } - + try { LuaValue[] keys = table.keys(); for (LuaValue k : keys) { try { LuaValue keyValue = table.get(k); - + T object = null; - + if (keyValue.istable()) { object = serialize(type, keyValue.checktable()); } else if (keyValue.isint()) { @@ -75,7 +123,7 @@ public class LuaSerializer implements Serializer { public T serialize(Class type, LuaTable table) { T object = null; - + if (type == List.class) { try { Class listType = (Class) type.getTypeParameters()[0].getClass(); @@ -85,7 +133,7 @@ public class LuaSerializer implements Serializer { return null; } } - + try { if (!methodAccessCache.containsKey(type)) { cacheType(type); @@ -98,7 +146,7 @@ public class LuaSerializer implements Serializer { if (table == null) { return object; } - + LuaValue[] keys = table.keys(); for (LuaValue k : keys) { try { @@ -131,7 +179,7 @@ public class LuaSerializer implements Serializer { Grasscutter.getLogger().info(ScriptUtils.toMap(table).toString()); e.printStackTrace(); } - + return object; } diff --git a/src/main/java/emu/grasscutter/scripts/serializer/Serializer.java b/src/main/java/emu/grasscutter/scripts/serializer/Serializer.java index 64bd266d2..3d5cd8845 100644 --- a/src/main/java/emu/grasscutter/scripts/serializer/Serializer.java +++ b/src/main/java/emu/grasscutter/scripts/serializer/Serializer.java @@ -1,12 +1,14 @@ package emu.grasscutter.scripts.serializer; import java.util.List; +import java.util.Map; -import org.luaj.vm2.LuaTable; public interface Serializer { - + public List toList(Class type, Object obj); - + public T toObject(Class type, Object obj); + + public Map toMap(Class type, Object obj); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAddQuestContentProgressReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAddQuestContentProgressReq.java index 03bae4ac5..cc600905c 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerAddQuestContentProgressReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerAddQuestContentProgressReq.java @@ -1,5 +1,8 @@ package emu.grasscutter.server.packet.recv; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketHandler; @@ -7,6 +10,9 @@ import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.AddQuestContentProgressReqOuterClass; import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.packet.send.PacketAddQuestContentProgressRsp; +import emu.grasscutter.data.excels.QuestData.QuestCondition; +import java.util.List; +import java.util.stream.Stream; @Opcodes(PacketOpcodes.AddQuestContentProgressReq) public class HandlerAddQuestContentProgressReq extends PacketHandler { @@ -14,9 +20,14 @@ public class HandlerAddQuestContentProgressReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { var req = AddQuestContentProgressReqOuterClass.AddQuestContentProgressReq.parseFrom(payload); - - session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.getContentTriggerByValue(req.getContentType()), req.getParam()); - + //Find all conditions in quest that are the same as the given one + Stream finishCond = GameData.getQuestDataMap().get(req.getParam()).getFinishCond().stream(); + Stream acceptCond = GameData.getQuestDataMap().get(req.getParam()).getAcceptCond().stream(); + Stream failCond = GameData.getQuestDataMap().get(req.getParam()).getFailCond().stream(); + List allCondMatch = Stream.concat(Stream.concat(acceptCond,failCond),finishCond).filter(p -> p.getType().getValue() == req.getContentType()).toList(); + for(QuestCondition cond : allCondMatch ) { + session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.getContentTriggerByValue(req.getContentType()), cond.getParam()); + } session.send(new PacketAddQuestContentProgressRsp(req.getContentType())); } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java index b9b8c8657..9be54256f 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEnterSceneDoneReq.java @@ -1,6 +1,8 @@ package emu.grasscutter.server.packet.recv; +import emu.grasscutter.Grasscutter; import emu.grasscutter.game.player.Player.SceneLoadState; +import emu.grasscutter.game.quest.QuestGroupSuite; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.packet.PacketHandler; @@ -16,7 +18,7 @@ public class HandlerEnterSceneDoneReq extends PacketHandler { session.getPlayer().setSceneLoadState(SceneLoadState.LOADED); // Done - session.send(new PacketEnterSceneDoneRsp(session.getPlayer())); + session.send(new PacketPlayerTimeNotify(session.getPlayer())); // Probably not the right place // Spawn player in world @@ -35,11 +37,15 @@ public class HandlerEnterSceneDoneReq extends PacketHandler { // notify client to load the npc for quest var questGroupSuites = session.getPlayer().getQuestManager().getSceneGroupSuite(session.getPlayer().getSceneId()); + session.getPlayer().getScene().loadGroupForQuest(questGroupSuites); + Grasscutter.getLogger().debug("Loaded Scene {} Quest(s) Groupsuite(s): {}", session.getPlayer().getSceneId(), questGroupSuites); session.send(new PacketGroupSuiteNotify(questGroupSuites)); // Reset timer for sending player locations session.getPlayer().resetSendPlayerLocTime(); + //Rsp + session.send(new PacketEnterSceneDoneRsp(session.getPlayer())); } } 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 3dae7fe10..933227d86 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,10 @@ package emu.grasscutter.server.packet.recv; +import emu.grasscutter.data.GameData; +import emu.grasscutter.data.binout.MainQuestData; import emu.grasscutter.game.inventory.GameItem; +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.enums.ParentQuestState; import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketOpcodes; @@ -11,16 +15,28 @@ import emu.grasscutter.server.packet.send.PacketNpcTalkRsp; @Opcodes(PacketOpcodes.NpcTalkReq) public class HandlerNpcTalkReq extends PacketHandler { - + @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { NpcTalkReq req = NpcTalkReq.parseFrom(payload); - - // Why are there 2 quest triggers that do the same thing... - session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_COMPLETE_TALK, req.getTalkId()); - session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_FINISH_PLOT, req.getTalkId()); + //Check if mainQuest exists + int talkId = req.getTalkId(); + //remove last 2 digits to get a mainQuestId + int mainQuestId = talkId/100; + MainQuestData mainQuestData = GameData.getMainQuestDataMap().get(mainQuestId); + if(mainQuestData != null) { + MainQuestData.TalkData talk = mainQuestData.getTalks().stream().filter(p -> p.getId() == talkId).toList().get(0); + if(talk != null) { + //talk is finished + session.getPlayer().getQuestManager().getMainQuestById(mainQuestId).getTalks().put(Integer.valueOf(talkId),talk); + session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_COMPLETE_ANY_TALK,String.valueOf(req.getTalkId()), 0, 0); + // Why are there 2 quest triggers that do the same thing... + session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_COMPLETE_TALK, req.getTalkId(),0); + session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_FINISH_PLOT, req.getTalkId(),0); + } + } session.send(new PacketNpcTalkRsp(req.getNpcEntityId(), req.getTalkId(), req.getEntityId())); } -} +} \ No newline at end of file diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPersonalLineAllDataReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPersonalLineAllDataReq.java index 5552e63f2..4db560c00 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPersonalLineAllDataReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPersonalLineAllDataReq.java @@ -11,7 +11,7 @@ public class HandlerPersonalLineAllDataReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { - session.send(new PacketPersonalLineAllDataRsp(session.getPlayer().getQuestManager().getQuests().values())); + session.send(new PacketPersonalLineAllDataRsp(session.getPlayer().getQuestManager().getMainQuests().values())); } } diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPostEnterSceneReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPostEnterSceneReq.java index 746f8c1da..efc254192 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerPostEnterSceneReq.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerPostEnterSceneReq.java @@ -14,7 +14,7 @@ public class HandlerPostEnterSceneReq extends PacketHandler { @Override public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { if(session.getPlayer().getScene().getSceneType() == SceneType.SCENE_ROOM){ - session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ENTER_ROOM, session.getPlayer().getSceneId()); + session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ENTER_ROOM, session.getPlayer().getSceneId(),0); } session.send(new PacketPostEnterSceneRsp(session.getPlayer())); diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerQuestUpdateQuestVarReq.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerQuestUpdateQuestVarReq.java new file mode 100644 index 000000000..4e804f422 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerQuestUpdateQuestVarReq.java @@ -0,0 +1,48 @@ +package emu.grasscutter.server.packet.recv; + +import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketHandler; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.PacketHeadOuterClass; +import emu.grasscutter.net.proto.PlayerSetPauseReqOuterClass; +import emu.grasscutter.net.proto.QuestUpdateQuestVarReqOuterClass; +import emu.grasscutter.net.proto.QuestVarOpOuterClass; +import emu.grasscutter.server.game.GameSession; +import emu.grasscutter.server.packet.send.PacketPlayerSetPauseRsp; +import emu.grasscutter.server.packet.send.PacketQuestUpdateQuestVarRsp; + +import java.util.List; + +@Opcodes(PacketOpcodes.QuestUpdateQuestVarReq) +public class HandlerQuestUpdateQuestVarReq extends PacketHandler { + + @Override + public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { + //Client sends packets. One with the value, and one with the index and the new value to set/inc/dec + var req = QuestUpdateQuestVarReqOuterClass.QuestUpdateQuestVarReq.parseFrom(payload); + GameMainQuest mainQuest = session.getPlayer().getQuestManager().getMainQuestById(req.getQuestId()/100); + List questVars = req.getQuestVarOpListList(); + if (mainQuest.getQuestVarsUpdate().size() == 0) { + for (QuestVarOpOuterClass.QuestVarOp questVar : questVars) { + mainQuest.getQuestVarsUpdate().add(questVar.getValue()); + } + } else { + for (QuestVarOpOuterClass.QuestVarOp questVar : questVars) { + if (questVar.getIsAdd()) { + if (questVar.getValue() >= 0) { + mainQuest.incQuestVar(questVar.getIndex(), questVar.getValue()); + } else { + mainQuest.decQuestVar(questVar.getIndex(), questVar.getValue()); + } + } else { + mainQuest.setQuestVar(questVar.getIndex(), mainQuest.getQuestVarsUpdate().get(0)); + } + //remove the first element from the update list + mainQuest.getQuestVarsUpdate().remove(0); + } + } + session.send(new PacketQuestUpdateQuestVarRsp(req.getQuestId())); + } + +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java index 7d64da48f..8ae369102 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestNotify.java @@ -2,21 +2,25 @@ package emu.grasscutter.server.packet.send; import emu.grasscutter.game.player.Player; import emu.grasscutter.game.quest.GameMainQuest; +import emu.grasscutter.game.quest.enums.ParentQuestState; 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()); + + for (GameMainQuest mainQuest : player.getQuestManager().getMainQuests().values()) { + //Canceled Quests do not appear in this packet + if(mainQuest.getState() != ParentQuestState.PARENT_QUEST_STATE_CANCELED) { + 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 index 68eab7222..45b375b79 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestUpdateNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketFinishedParentQuestUpdateNotify.java @@ -5,15 +5,29 @@ import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.FinishedParentQuestUpdateNotifyOuterClass.FinishedParentQuestUpdateNotify; +import java.util.List; + public class PacketFinishedParentQuestUpdateNotify extends BasePacket { - + public PacketFinishedParentQuestUpdateNotify(GameMainQuest quest) { super(PacketOpcodes.FinishedParentQuestUpdateNotify); - + FinishedParentQuestUpdateNotify proto = FinishedParentQuestUpdateNotify.newBuilder() .addParentQuestList(quest.toProto()) .build(); - + this.setData(proto); } + + public PacketFinishedParentQuestUpdateNotify(List quests) { + super(PacketOpcodes.FinishedParentQuestUpdateNotify); + + var proto = FinishedParentQuestUpdateNotify.newBuilder(); + + for(GameMainQuest mainQuest : quests) { + proto.addParentQuestList(mainQuest.toProto()); + } + proto.build(); + this.setData(proto); + } } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketPersonalLineAllDataRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketPersonalLineAllDataRsp.java index 6074c1cf1..35e9195f6 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketPersonalLineAllDataRsp.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketPersonalLineAllDataRsp.java @@ -23,7 +23,7 @@ public class PacketPersonalLineAllDataRsp extends BasePacket { .map(GameMainQuest::getChildQuests) .map(Map::values) .flatMap(Collection::stream) - .map(GameQuest::getQuestId) + .map(GameQuest::getSubQuestId) .collect(Collectors.toSet()); GameData.getPersonalLineDataMap().values().stream() diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListNotify.java index ccf0d765a..27009fd97 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListNotify.java @@ -3,21 +3,24 @@ 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.game.quest.enums.QuestState; 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()); + if(quest.getState() != QuestState.QUEST_STATE_UNSTARTED) { + 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 index adc0767a8..d51d6eb1c 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListUpdateNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestListUpdateNotify.java @@ -6,15 +6,28 @@ import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.QuestListUpdateNotifyOuterClass.QuestListUpdateNotify; +import java.util.List; + public class PacketQuestListUpdateNotify extends BasePacket { - + public PacketQuestListUpdateNotify(GameQuest quest) { super(PacketOpcodes.QuestListUpdateNotify); QuestListUpdateNotify proto = QuestListUpdateNotify.newBuilder() .addQuestList(quest.toProto()) .build(); - + this.setData(proto); } + + public PacketQuestListUpdateNotify(List quests) { + super(PacketOpcodes.QuestListUpdateNotify); + var proto = QuestListUpdateNotify.newBuilder(); + for(GameQuest quest : quests) { + proto.addQuestList(quest.toProto()); + } + proto.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 index 76ee56316..70ba30f36 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketQuestProgressUpdateNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestProgressUpdateNotify.java @@ -7,24 +7,24 @@ 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()); - + + QuestProgressUpdateNotify.Builder proto = QuestProgressUpdateNotify.newBuilder().setQuestId(quest.getSubQuestId()); + 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/PacketQuestUpdateQuestVarRsp.java b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestUpdateQuestVarRsp.java new file mode 100644 index 000000000..22f672d28 --- /dev/null +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketQuestUpdateQuestVarRsp.java @@ -0,0 +1,19 @@ +package emu.grasscutter.server.packet.send; + +import emu.grasscutter.net.packet.BasePacket; +import emu.grasscutter.net.packet.Opcodes; +import emu.grasscutter.net.packet.PacketOpcodes; +import emu.grasscutter.net.proto.QuestUpdateQuestVarReqOuterClass; +import emu.grasscutter.net.proto.QuestUpdateQuestVarRspOuterClass; + +@Opcodes(PacketOpcodes.QuestUpdateQuestVarReq) +public class PacketQuestUpdateQuestVarRsp extends BasePacket { + + + public PacketQuestUpdateQuestVarRsp(int questId) { + super(PacketOpcodes.QuestUpdateQuestVarRsp); + var rsp = QuestUpdateQuestVarRspOuterClass.QuestUpdateQuestVarRsp.newBuilder() + .setQuestId(questId).build(); + this.setData(rsp); + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java index 278d4b04b..bd40e3226 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketServerCondMeetQuestListUpdateNotify.java @@ -6,8 +6,11 @@ import emu.grasscutter.net.packet.BasePacket; import emu.grasscutter.net.packet.PacketOpcodes; import emu.grasscutter.net.proto.ServerCondMeetQuestListUpdateNotifyOuterClass.ServerCondMeetQuestListUpdateNotify; +import java.util.List; +import java.util.Set; + public class PacketServerCondMeetQuestListUpdateNotify extends BasePacket { - + public PacketServerCondMeetQuestListUpdateNotify(Player player) { super(PacketOpcodes.ServerCondMeetQuestListUpdateNotify); @@ -23,13 +26,24 @@ public class PacketServerCondMeetQuestListUpdateNotify extends BasePacket { this.setData(proto); } - - public PacketServerCondMeetQuestListUpdateNotify(GameQuest quest) { + + public PacketServerCondMeetQuestListUpdateNotify(List quests) { super(PacketOpcodes.ServerCondMeetQuestListUpdateNotify); - ServerCondMeetQuestListUpdateNotify proto = ServerCondMeetQuestListUpdateNotify.newBuilder() - //.addAddQuestIdList(quest.getQuestId()) - .build(); + ServerCondMeetQuestListUpdateNotify.Builder proto = ServerCondMeetQuestListUpdateNotify.newBuilder(); + for (GameQuest quest : quests) { + proto.addAddQuestIdList(quest.getSubQuestId()); + } + proto.build(); + + this.setData(proto); + } + + public PacketServerCondMeetQuestListUpdateNotify() { + super(PacketOpcodes.ServerCondMeetQuestListUpdateNotify); + + ServerCondMeetQuestListUpdateNotify.Builder proto = ServerCondMeetQuestListUpdateNotify.newBuilder(); + proto.build(); this.setData(proto); }