Skip to content
Snippets Groups Projects
Commit 8050f0cc authored by akatatsu27's avatar akatatsu27
Browse files

and misc bug fixes

parent 02a56fce
Branches
Tags
No related merge requests found
Showing
with 967 additions and 455 deletions
...@@ -56,22 +56,23 @@ public class ResourceLoader { ...@@ -56,22 +56,23 @@ public class ResourceLoader {
public static void loadAll() { public static void loadAll() {
Grasscutter.getLogger().info(translate("messages.status.resources.loading")); Grasscutter.getLogger().info(translate("messages.status.resources.loading"));
// Load ability lists // Load ability lists
loadAbilityEmbryos(); loadAbilityEmbryos();
loadOpenConfig(); loadOpenConfig();
loadAbilityModifiers(); loadAbilityModifiers();
// Load resources // Load resources
loadResources(); loadResources();
// Process into depots // Process into depots
GameDepot.load(); GameDepot.load();
// Load spawn data and quests // Load spawn data and quests
loadSpawnData(); loadSpawnData();
loadQuests(); loadQuests();
// Load scene points - must be done AFTER resources are loaded loadScriptSceneData();
loadScenePoints(); // Load scene points - must be done AFTER resources are loaded
// Load default home layout loadScenePoints();
loadHomeworldDefaultSaveData(); // Load default home layout
loadNpcBornData(); loadHomeworldDefaultSaveData();
loadNpcBornData();
Grasscutter.getLogger().info(translate("messages.status.resources.finish")); Grasscutter.getLogger().info(translate("messages.status.resources.finish"));
} }
...@@ -420,10 +421,32 @@ public class ResourceLoader { ...@@ -420,10 +421,32 @@ public class ResourceLoader {
Grasscutter.getLogger().debug("Loaded " + GameData.getMainQuestDataMap().size() + " MainQuestDatas."); Grasscutter.getLogger().debug("Loaded " + GameData.getMainQuestDataMap().size() + " MainQuestDatas.");
} }
@SneakyThrows public static void loadScriptSceneData() {
private static void loadHomeworldDefaultSaveData() { File folder = new File(RESOURCE("ScriptSceneData/"));
var folder = Files.list(Path.of(RESOURCE("BinOutput/HomeworldDefaultSave"))).toList();
var pattern = Pattern.compile("scene(.*)_home_config.json"); 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) { for (var file : folder) {
var matcher = pattern.matcher(file.getFileName().toString()); var matcher = pattern.matcher(file.getFileName().toString());
......
package emu.grasscutter.data.binout; package emu.grasscutter.data.binout;
import dev.morphia.annotations.Entity;
import emu.grasscutter.game.quest.enums.QuestType; import emu.grasscutter.game.quest.enums.QuestType;
import lombok.Data; import lombok.Data;
import java.util.List;
import java.util.Objects;
public class MainQuestData { public class MainQuestData {
private int id; private int id;
private int ICLLDPJFIMA;
private int series; private int series;
private QuestType type; private QuestType type;
private long titleTextMapHash; private long titleTextMapHash;
private int[] suggestTrackMainQuestList; private int[] suggestTrackMainQuestList;
private int[] rewardIdList; private int[] rewardIdList;
private SubQuestData[] subQuests; private SubQuestData[] subQuests;
private List<TalkData> talks;
public int getId() { private long[] preloadLuaList;
return id;
} public int getId() {
return id;
}
public int getSeries() { public int getSeries() {
return series; return series;
} }
public QuestType getType() { public QuestType getType() {
return type; return type;
} }
public long getTitleTextMapHash() { public long getTitleTextMapHash() {
return titleTextMapHash; return titleTextMapHash;
} }
public int[] getSuggestTrackMainQuestList() { public int[] getSuggestTrackMainQuestList() {
return suggestTrackMainQuestList; return suggestTrackMainQuestList;
} }
...@@ -37,14 +43,28 @@ public class MainQuestData { ...@@ -37,14 +43,28 @@ public class MainQuestData {
public int[] getRewardIdList() { public int[] getRewardIdList() {
return rewardIdList; return rewardIdList;
} }
public SubQuestData[] getSubQuests() { public SubQuestData[] getSubQuests() {
return subQuests; return subQuests;
} }
public List<TalkData> getTalks() {
return talks;
}
public void onLoad() {
this.talks = talks.stream().filter(Objects::nonNull).toList();
}
@Data @Data
public static class SubQuestData { public static class SubQuestData {
private int subId; private int subId;
private int order; private int order;
} }
@Data @Entity
public static class TalkData {
private int id;
private String heroTalk;
}
} }
...@@ -36,10 +36,14 @@ public class QuestData extends GameResource { ...@@ -36,10 +36,14 @@ public class QuestData extends GameResource {
private List<QuestExecParam> beginExec; private List<QuestExecParam> beginExec;
private List<QuestExecParam> finishExec; private List<QuestExecParam> finishExec;
private List<QuestExecParam> failExec; private List<QuestExecParam> failExec;
private Guide guide;
//ResourceLoader not happy if you remove getId() ~~
public int getId() { public int getId() {
return subId; return subId;
} }
//Added getSubId() for clarity
public int getSubId() {return subId;}
public int getMainId() { public int getMainId() {
return mainId; return mainId;
...@@ -62,7 +66,7 @@ public class QuestData extends GameResource { ...@@ -62,7 +66,7 @@ public class QuestData extends GameResource {
} }
public LogicType getAcceptCondComb() { public LogicType getAcceptCondComb() {
return acceptCondComb; return acceptCondComb == null ? LogicType.LOGIC_NONE : acceptCondComb;
} }
public List<QuestCondition> getAcceptCond() { public List<QuestCondition> getAcceptCond() {
...@@ -70,7 +74,7 @@ public class QuestData extends GameResource { ...@@ -70,7 +74,7 @@ public class QuestData extends GameResource {
} }
public LogicType getFinishCondComb() { public LogicType getFinishCondComb() {
return finishCondComb; return finishCondComb == null ? LogicType.LOGIC_NONE : finishCondComb;
} }
public List<QuestCondition> getFinishCond() { public List<QuestCondition> getFinishCond() {
...@@ -78,7 +82,7 @@ public class QuestData extends GameResource { ...@@ -78,7 +82,7 @@ public class QuestData extends GameResource {
} }
public LogicType getFailCondComb() { public LogicType getFailCondComb() {
return failCondComb; return failCondComb == null ? LogicType.LOGIC_NONE : failCondComb;
} }
public List<QuestCondition> getFailCond() { public List<QuestCondition> getFailCond() {
...@@ -118,4 +122,11 @@ public class QuestData extends GameResource { ...@@ -118,4 +122,11 @@ public class QuestData extends GameResource {
private String count; private String count;
} }
@Data
public static class Guide {
private String type;
private List<String> param;
private int guideScene;
}
} }
...@@ -14,12 +14,8 @@ import emu.grasscutter.game.activity.ActivityManager; ...@@ -14,12 +14,8 @@ import emu.grasscutter.game.activity.ActivityManager;
import emu.grasscutter.game.avatar.Avatar; import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.avatar.AvatarStorage; import emu.grasscutter.game.avatar.AvatarStorage;
import emu.grasscutter.game.battlepass.BattlePassManager; import emu.grasscutter.game.battlepass.BattlePassManager;
import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.entity.EntityVehicle;
import emu.grasscutter.game.home.GameHome; 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.expedition.ExpeditionInfo;
import emu.grasscutter.game.friends.FriendsList; import emu.grasscutter.game.friends.FriendsList;
import emu.grasscutter.game.friends.PlayerProfile; import emu.grasscutter.game.friends.PlayerProfile;
...@@ -43,6 +39,7 @@ import emu.grasscutter.game.props.ClimateType; ...@@ -43,6 +39,7 @@ import emu.grasscutter.game.props.ClimateType;
import emu.grasscutter.game.props.PlayerProperty; import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.WatcherTriggerType; import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.quest.QuestManager; import emu.grasscutter.game.quest.QuestManager;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.shop.ShopLimit; import emu.grasscutter.game.shop.ShopLimit;
import emu.grasscutter.game.tower.TowerData; import emu.grasscutter.game.tower.TowerData;
import emu.grasscutter.game.tower.TowerManager; import emu.grasscutter.game.tower.TowerManager;
...@@ -61,6 +58,7 @@ import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo ...@@ -61,6 +58,7 @@ import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo
import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture; import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture;
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail; import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
import emu.grasscutter.net.proto.VisionTypeOuterClass.VisionType; 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.PlayerJoinEvent;
import emu.grasscutter.server.event.player.PlayerQuitEvent; import emu.grasscutter.server.event.player.PlayerQuitEvent;
import emu.grasscutter.server.game.GameServer; import emu.grasscutter.server.game.GameServer;
...@@ -74,6 +72,7 @@ import emu.grasscutter.utils.Utils; ...@@ -74,6 +72,7 @@ import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import static emu.grasscutter.config.Configuration.*; import static emu.grasscutter.config.Configuration.*;
...@@ -122,6 +121,7 @@ public class Player { ...@@ -122,6 +121,7 @@ public class Player {
@Getter private Map<Long, ExpeditionInfo> expeditionInfo; @Getter private Map<Long, ExpeditionInfo> expeditionInfo;
@Getter private Map<Integer, Integer> unlockedRecipies; @Getter private Map<Integer, Integer> unlockedRecipies;
@Getter private List<ActiveForgeData> activeForges; @Getter private List<ActiveForgeData> activeForges;
@Getter private Map<Integer,Integer> questGlobalVariables;
@Transient private long nextGuid = 0; @Transient private long nextGuid = 0;
@Transient private int peerId; @Transient private int peerId;
...@@ -151,7 +151,7 @@ public class Player { ...@@ -151,7 +151,7 @@ public class Player {
@Getter private transient CookingManager cookingManager; @Getter private transient CookingManager cookingManager;
@Getter private transient ActivityManager activityManager; @Getter private transient ActivityManager activityManager;
@Getter private transient PlayerBuffManager buffManager; @Getter private transient PlayerBuffManager buffManager;
// Manager data (Save-able to the database) // Manager data (Save-able to the database)
private PlayerProfile playerProfile; private PlayerProfile playerProfile;
private TeamManager teamManager; private TeamManager teamManager;
...@@ -579,9 +579,38 @@ public class Player { ...@@ -579,9 +579,38 @@ public class Player {
return towerData; return towerData;
} }
public PlayerGachaInfo getGachaInfo() { public void setQuestManager(QuestManager questManager) {
return gachaInfo; 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() { public PlayerProfile getProfile() {
if (this.playerProfile == null) { if (this.playerProfile == null) {
...@@ -892,7 +921,7 @@ public class Player { ...@@ -892,7 +921,7 @@ public class Player {
public boolean hasSentLoginPackets() { public boolean hasSentLoginPackets() {
return hasSentLoginPackets; return hasSentLoginPackets;
} }
public void addAvatar(Avatar avatar, boolean addToCurrentTeam) { public void addAvatar(Avatar avatar, boolean addToCurrentTeam) {
boolean result = getAvatars().addAvatar(avatar); boolean result = getAvatars().addAvatar(avatar);
...@@ -1327,21 +1356,25 @@ public class Player { ...@@ -1327,21 +1356,25 @@ public class Player {
// Execute daily reset logic if this is a new day. // Execute daily reset logic if this is a new day.
this.doDailyReset(); this.doDailyReset();
// Packets
session.send(new PacketPlayerDataNotify(this)); // Player data // Rewind active quests, and put the player to the first rewind position it finds (if any) of an active quest
session.send(new PacketStoreWeightLimitNotify()); getQuestManager().onLogin();
session.send(new PacketPlayerStoreNotify(this));
session.send(new PacketAvatarDataNotify(this)); // Packets
session.send(new PacketFinishedParentQuestNotify(this)); session.send(new PacketPlayerDataNotify(this)); // Player data
session.send(new PacketBattlePassAllDataNotify(this)); session.send(new PacketStoreWeightLimitNotify());
session.send(new PacketQuestListNotify(this)); session.send(new PacketPlayerStoreNotify(this));
session.send(new PacketCodexDataFullNotify(this)); session.send(new PacketAvatarDataNotify(this));
session.send(new PacketAllWidgetDataNotify(this)); session.send(new PacketFinishedParentQuestNotify(this));
session.send(new PacketWidgetGadgetAllDataNotify()); session.send(new PacketBattlePassAllDataNotify(this));
session.send(new PacketCombineDataNotify(this.unlockedCombines)); session.send(new PacketQuestListNotify(this));
this.forgingManager.sendForgeDataNotify(); session.send(new PacketCodexDataFullNotify(this));
this.resinManager.onPlayerLogin(); session.send(new PacketAllWidgetDataNotify(this));
this.cookingManager.sendCookDataNofity(); session.send(new PacketWidgetGadgetAllDataNotify());
session.send(new PacketCombineDataNotify(this.unlockedCombines));
this.forgingManager.sendForgeDataNotify();
this.resinManager.onPlayerLogin();
this.cookingManager.sendCookDataNofity();
// Unlock in case this is an existing user that reached a level before we implemented unlocking. // Unlock in case this is an existing user that reached a level before we implemented unlocking.
this.getOpenStateManager().unlockLevelDependentStates(); this.getOpenStateManager().unlockLevelDependentStates();
......
...@@ -2,7 +2,15 @@ package emu.grasscutter.game.quest; ...@@ -2,7 +2,15 @@ package emu.grasscutter.game.quest;
import java.util.*; 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.server.packet.send.PacketCodexDataUpdateNotify;
import emu.grasscutter.utils.Position;
import lombok.Getter;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
import dev.morphia.annotations.Entity; import dev.morphia.annotations.Entity;
...@@ -11,6 +19,7 @@ import dev.morphia.annotations.Indexed; ...@@ -11,6 +19,7 @@ import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.Transient; import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.MainQuestData; import emu.grasscutter.data.binout.MainQuestData;
import emu.grasscutter.data.binout.MainQuestData.*;
import emu.grasscutter.data.excels.RewardData; import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
...@@ -23,24 +32,33 @@ import emu.grasscutter.net.proto.QuestOuterClass.Quest; ...@@ -23,24 +32,33 @@ import emu.grasscutter.net.proto.QuestOuterClass.Quest;
import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify; import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify;
import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify; import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify;
import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify; import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import javax.script.Bindings;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import javax.script.CompiledScript;
import javax.script.ScriptException;
import static emu.grasscutter.config.Configuration.SCRIPT;
@Entity(value = "quests", useDiscriminator = false) @Entity(value = "quests", useDiscriminator = false)
public class GameMainQuest { public class GameMainQuest {
@Id private ObjectId id; @Id private ObjectId id;
@Indexed @Getter private int ownerUid;
@Indexed private int ownerUid; @Transient @Getter private Player owner;
@Transient private Player owner; @Transient @Getter private QuestManager questManager;
@Getter private Map<Integer, GameQuest> childQuests;
private Map<Integer, GameQuest> childQuests; @Getter private int parentQuestId;
@Getter private int[] questVars;
private int parentQuestId; //QuestUpdateQuestVarReq is sent in two stages...
private int[] questVars; @Getter private List<Integer> questVarsUpdate;
private ParentQuestState state; @Getter private ParentQuestState state;
private boolean isFinished; @Getter private boolean isFinished;
List<QuestGroupSuite> questGroupSuites; @Getter List<QuestGroupSuite> questGroupSuites;
@Getter int[] suggestTrackMainQuestList;
@Getter private Map<Integer,TalkData> talks;
//key is subId
private Map<Integer,Position> rewindPositions;
private Map<Integer,Position> rewindRotations;
@Deprecated // Morphia only. Do not use. @Deprecated // Morphia only. Do not use.
public GameMainQuest() {} public GameMainQuest() {}
...@@ -48,52 +66,60 @@ public class GameMainQuest { ...@@ -48,52 +66,60 @@ public class GameMainQuest {
public GameMainQuest(Player player, int parentQuestId) { public GameMainQuest(Player player, int parentQuestId) {
this.owner = player; this.owner = player;
this.ownerUid = player.getUid(); this.ownerUid = player.getUid();
this.questManager = player.getQuestManager();
this.parentQuestId = parentQuestId; this.parentQuestId = parentQuestId;
this.childQuests = new HashMap<>(); 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.state = ParentQuestState.PARENT_QUEST_STATE_NONE;
this.questGroupSuites = new ArrayList<>(); this.questGroupSuites = new ArrayList<>();
this.rewindPositions = new HashMap<>();
this.rewindRotations = new HashMap<>();
addAllChildQuests();
addRewindPoints();
} }
public int getParentQuestId() { private void addAllChildQuests() {
return parentQuestId; List<Integer> subQuestIds = Arrays.stream(GameData.getMainQuestDataMap().get(this.parentQuestId).getSubQuests()).map(SubQuestData::getSubId).toList();
} for (Integer subQuestId : subQuestIds) {
QuestData questConfig = GameData.getQuestDataMap().get(subQuestId);
public int getOwnerUid() { this.childQuests.put(subQuestId, new GameQuest(this, questConfig));
return ownerUid; }
} }
public Player getOwner() {
return owner;
}
public void setOwner(Player player) { public void setOwner(Player player) {
if (player.getUid() != this.getOwnerUid()) return; if (player.getUid() != this.getOwnerUid()) return;
this.owner = player; this.owner = player;
} }
public Map<Integer, GameQuest> getChildQuests() { public int getQuestVar(int i) {
return childQuests; 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 GameQuest getChildQuestById(int id) { public void incQuestVar(int i, int inc) {
return this.getChildQuests().get(id); int previousValue = this.questVars[i];
} this.questVars[i] += inc;
Grasscutter.getLogger().debug("questVar {} value incremented from {} to {}", i, previousValue, previousValue + inc);
}
public int[] getQuestVars() { public void decQuestVar(int i, int dec) {
return questVars; int previousValue = this.questVars[i];
} this.questVars[i] -= dec;
Grasscutter.getLogger().debug("questVar {} value decremented from {} to {}", i, previousValue, previousValue - dec);
}
public ParentQuestState getState() {
return state;
}
public boolean isFinished() { public GameQuest getChildQuestById(int id) {
return isFinished; return this.getChildQuests().get(id);
} }
public GameQuest getChildQuestByOrder(int order) {
public List<QuestGroupSuite> getQuestGroupSuites() { return this.getChildQuests().values().stream().filter(p -> p.getQuestData().getOrder() == order).toList().get(0);
return questGroupSuites;
} }
public void finish() { public void finish() {
...@@ -120,9 +146,193 @@ public class GameMainQuest { ...@@ -120,9 +146,193 @@ public class GameMainQuest {
// handoff main quest // handoff main quest
if(mainQuestData.getSuggestTrackMainQuestList() != null){ if(mainQuestData.getSuggestTrackMainQuestList() != null){
Arrays.stream(mainQuestData.getSuggestTrackMainQuestList()) 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<Position> rewind() {
if(this.questManager == null) {
this.questManager = getOwner().getQuestManager();
}
List<GameQuest> 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<GameQuest> 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<Position> 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<String, List<Float>> dummyPointMap = dummyPointScript.getDummyPoints();
if (dummyPointMap != null) {
List<Float> avatarPosPos = dummyPointMap.get(avatarPos + ".pos");
if (avatarPosPos != null) {
Position pos = new Position(avatarPosPos.get(0),avatarPosPos.get(1),avatarPosPos.get(2));
List<Float> 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<GameQuest> 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<QuestData.QuestCondition> 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<GameQuest> 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<QuestData.QuestCondition> 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<GameQuest> 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<QuestData.QuestCondition> 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() { public void save() {
DatabaseHelper.saveQuest(this); DatabaseHelper.saveQuest(this);
...@@ -131,24 +341,30 @@ public class GameMainQuest { ...@@ -131,24 +341,30 @@ public class GameMainQuest {
public ParentQuest toProto() { public ParentQuest toProto() {
ParentQuest.Builder proto = ParentQuest.newBuilder() ParentQuest.Builder proto = ParentQuest.newBuilder()
.setParentQuestId(getParentQuestId()) .setParentQuestId(getParentQuestId())
.setIsFinished(isFinished()) .setIsFinished(isFinished());
.setParentQuestState(getState().getValue()); /**
if ParentQuestState is NONE, official server does not send ParentQuestState nor childQuestList!!!
for (GameQuest quest : this.getChildQuests().values()) { might need more sniffing...
ChildQuest childQuest = ChildQuest.newBuilder() sending childQuestList without ParentQuestState set causes the game to hang on login
.setQuestId(quest.getQuestId()) */
.setState(quest.getState().getValue()) if (getState() != ParentQuestState.PARENT_QUEST_STATE_NONE) {
.build(); proto.setParentQuestState(getState().getValue());
for (GameQuest quest : this.getChildQuests().values()) {
proto.addChildQuestList(childQuest); if (quest.getState() != QuestState.QUEST_STATE_UNSTARTED) {
} ChildQuest childQuest = ChildQuest.newBuilder()
.setQuestId(quest.getSubQuestId())
if (getQuestVars() != null) { .setState(quest.getState().getValue())
for (int i : getQuestVars()) { .build();
proto.addQuestVar(i);
} proto.addChildQuestList(childQuest);
} }
}
}
for (int i : getQuestVars()) {
proto.addQuestVar(i);
}
return proto.build(); return proto.build();
} }
} }
...@@ -4,51 +4,84 @@ import dev.morphia.annotations.Entity; ...@@ -4,51 +4,84 @@ import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient; import dev.morphia.annotations.Transient;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; 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.ChapterData;
import emu.grasscutter.data.excels.QuestData; 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.player.Player;
import emu.grasscutter.game.quest.enums.LogicType;
import emu.grasscutter.game.quest.enums.QuestState; import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.net.proto.ChapterStateOuterClass; import emu.grasscutter.net.proto.ChapterStateOuterClass;
import emu.grasscutter.net.proto.QuestOuterClass.Quest; 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.PacketChapterStateNotify;
import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify; import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify;
import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify; import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify;
import emu.grasscutter.utils.Utils; 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 @Entity
public class GameQuest { public class GameQuest {
@Transient private GameMainQuest mainQuest; @Transient @Getter @Setter private GameMainQuest mainQuest;
@Transient private QuestData questData; @Transient @Getter private QuestData questData;
private int questId; @Getter private int subQuestId;
private int mainQuestId; @Getter private int mainQuestId;
@Getter @Setter
private QuestState state; private QuestState state;
private int startTime; @Getter @Setter private int startTime;
private int acceptTime; @Getter @Setter private int acceptTime;
private int finishTime; @Getter @Setter private int finishTime;
private int[] finishProgressList; @Getter private int[] finishProgressList;
private int[] failProgressList; @Getter private int[] failProgressList;
@Transient @Getter private Map<String, TriggerExcelConfigData> triggerData;
@Getter private Map<String, Boolean> triggers;
private transient Bindings bindings;
@Deprecated // Morphia only. Do not use. @Deprecated // Morphia only. Do not use.
public GameQuest() {} public GameQuest() {}
public GameQuest(GameMainQuest mainQuest, QuestData questData) { public GameQuest(GameMainQuest mainQuest, QuestData questData) {
this.mainQuest = mainQuest; this.mainQuest = mainQuest;
this.questId = questData.getId(); this.subQuestId = questData.getId();
this.mainQuestId = questData.getMainId(); this.mainQuestId = questData.getMainId();
this.questData = questData; 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.acceptTime = Utils.getCurrentSeconds();
this.startTime = this.acceptTime; this.startTime = this.acceptTime;
this.state = QuestState.QUEST_STATE_UNFINISHED; this.state = QuestState.QUEST_STATE_UNFINISHED;
List<QuestData.QuestCondition> 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()]; this.finishProgressList = new int[questData.getFinishCond().size()];
} }
...@@ -56,201 +89,136 @@ public class GameQuest { ...@@ -56,201 +89,136 @@ public class GameQuest {
this.failProgressList = new int[questData.getFailCond().size()]; 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( mainQuest.getOwner().sendPacket(new PacketChapterStateNotify(
ChapterData.beginQuestChapterMap.get(questId).getId(), ChapterData.beginQuestChapterMap.get(subQuestId).getId(),
ChapterStateOuterClass.ChapterState.CHAPTER_STATE_BEGIN 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);
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() { Grasscutter.getLogger().debug("Quest {} is started", subQuestId);
return finishTime;
} }
public void setFinishTime(int finishTime) { public String getTriggerNameById(int id) {
this.finishTime = finishTime; TriggerExcelConfigData trigger = GameData.getTriggerExcelConfigDataMap().get(id);
if(trigger != null) {
String triggerName = trigger.getTriggerName();
return triggerName;
}
//return empty string if can't find trigger
return "";
} }
public int[] getFinishProgressList() { public Player getOwner() {
return finishProgressList; return this.getMainQuest().getOwner();
} }
public void setFinishProgress(int index, int value) { public void setConfig(QuestData config) {
finishProgressList[index] = value; if (getSubQuestId() != config.getId()) return;
} this.questData = config;
}
public int[] getFailProgressList() { public void setFinishProgress(int index, int value) {
return failProgressList; finishProgressList[index] = value;
} }
public void setFailProgress(int index, int value) { public void setFailProgress(int index, int value) {
failProgressList[index] = value; failProgressList[index] = value;
} }
public void finish() { public void finish() {
this.state = QuestState.QUEST_STATE_FINISHED; this.state = QuestState.QUEST_STATE_FINISHED;
this.finishTime = Utils.getCurrentSeconds(); this.finishTime = Utils.getCurrentSeconds();
if (this.getFinishProgressList() != null) { if (getFinishProgressList() != null) {
for (int i = 0 ; i < getFinishProgressList().length; i++) { Arrays.fill(getFinishProgressList(), 1);
getFinishProgressList()[i] = 1; }
}
}
this.getOwner().getSession().send(new PacketQuestProgressUpdateNotify(this)); getOwner().getSession().send(new PacketQuestProgressUpdateNotify(this));
this.getOwner().getSession().send(new PacketQuestListUpdateNotify(this));
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();
}
this.getData().getFinishExec().forEach(e -> getOwner().getServer().getQuestSystem().triggerExec(this, e, e.getParam())); 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();
}
this.getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_QUEST_STATE_EQUAL, this.questId, this.state.getValue()); 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(questId)) { if (ChapterData.endQuestChapterMap.containsKey(subQuestId)){
mainQuest.getOwner().sendPacket(new PacketChapterStateNotify( mainQuest.getOwner().sendPacket(new PacketChapterStateNotify(
ChapterData.endQuestChapterMap.get(questId).getId(), ChapterData.endQuestChapterMap.get(subQuestId).getId(),
ChapterStateOuterClass.ChapterState.CHAPTER_STATE_END 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());
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; //TODO
} public void fail() {
this.state = QuestState.QUEST_STATE_FAILED;
boolean shouldAccept = LogicType.calculate(questData.getAcceptCondComb(), accept); this.finishTime = Utils.getCurrentSeconds();
if (shouldAccept) { if (getFailProgressList() != null) {
this.getOwner().getQuestManager().addQuest(questData.getId()); Arrays.fill(getFailProgressList(), 1);
}
}
}
} catch (Exception e) {
Grasscutter.getLogger().error("An error occurred while trying to accept quest.", e);
} }
return false; getOwner().getSession().send(new PacketQuestProgressUpdateNotify(this));
}
public void save() {
getMainQuest().save();
}
public Quest toProto() { getQuestData().getFailExec().forEach(e -> getOwner().getServer().getQuestSystem().triggerExec(this, e, e.getParam()));
Quest.Builder proto = Quest.newBuilder() //Some subQuests have conditions that subQuests fail (even from different MainQuests)
.setQuestId(this.getQuestId()) getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_QUEST_STATE_EQUAL, this.subQuestId, this.state.getValue(),0,0,0);
.setState(this.getState().getValue()) getOwner().getQuestManager().triggerEvent(QuestTrigger.QUEST_COND_STATE_EQUAL, this.subQuestId, this.state.getValue(),0,0,0);
.setParentQuestId(this.getMainQuestId())
.setStartTime(this.getStartTime())
.setStartGameTime(438)
.setAcceptTime(this.getAcceptTime());
if (this.getFinishProgressList() != null) {
for (int i : this.getFinishProgressList()) {
proto.addFinishProgressList(i);
}
}
if (this.getFailProgressList() != null) { }
for (int i : this.getFailProgressList()) { // Return true if ParentQuest should rewind to this childQuest
proto.addFailProgressList(i); 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;
return proto.build();
} }
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());
if (getFinishProgressList() != null) {
for (int i : getFinishProgressList()) {
proto.addFinishProgressList(i);
}
}
if (getFailProgressList() != null) {
for (int i : getFailProgressList()) {
proto.addFailProgressList(i);
}
}
return proto.build();
}
} }
package emu.grasscutter.game.quest; package emu.grasscutter.game.quest;
import java.beans.Transient;
import java.util.*; import java.util.*;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
...@@ -17,32 +18,127 @@ import emu.grasscutter.game.quest.enums.QuestTrigger; ...@@ -17,32 +18,127 @@ import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.quest.enums.LogicType; import emu.grasscutter.game.quest.enums.LogicType;
import emu.grasscutter.game.quest.enums.QuestState; import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.server.packet.send.*; import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import jdk.jshell.spi.ExecutionControl;
import lombok.Getter;
public class QuestManager extends BasePlayerManager { public class QuestManager extends BasePlayerManager {
private final Int2ObjectMap<GameMainQuest> quests; @Getter private final Player player;
@Getter private Map<Integer,Integer> questGlobalVariables;
public QuestManager(Player player) {
@Getter private final Int2ObjectMap<GameMainQuest> mainQuests;
@Getter private List<GameQuest> 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<Integer> 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<Integer> 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 QuestManager(Player player) {
super(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<GameMainQuest> newQuests = this.addMultMainQuests(newPlayerMainQuests);
//getPlayer().sendPacket(new PacketServerCondMeetQuestListUpdateNotify(newPlayerServerCondMeetQuestListUpdateNotify));
getPlayer().sendPacket(new PacketFinishedParentQuestUpdateNotify(newQuests));
}
public void onLogin() {
List<GameMainQuest> activeQuests = getActiveMainQuests();
for(GameMainQuest quest : activeQuests) {
List<Position> rewindPos = quest.rewind(); // <pos, rotation>
if(rewindPos != null) {
getPlayer().getPosition().set(rewindPos.get(0));
getPlayer().getRotation().set(rewindPos.get(1));
}
}
}
private List<GameMainQuest> addMultMainQuests(Set<Integer> mainQuestIds) {
List<GameMainQuest> 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;
} }
public Int2ObjectMap<GameMainQuest> getQuests() { /*
return quests; 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 GameMainQuest getMainQuestById(int mainQuestId) { public void setQuestGlobalVarValue(Integer variable, Integer value) {
return getQuests().get(mainQuestId); 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) { public GameQuest getQuestById(int questId) {
QuestData questConfig = GameData.getQuestDataMap().get(questId); QuestData questConfig = GameData.getQuestDataMap().get(questId);
if (questConfig == null) { if (questConfig == null) {
return null; return null;
} }
GameMainQuest mainQuest = getQuests().get(questConfig.getMainId()); GameMainQuest mainQuest = getMainQuests().get(questConfig.getMainId());
if (mainQuest == null) { if (mainQuest == null) {
return null; return null;
...@@ -51,34 +147,34 @@ public class QuestManager extends BasePlayerManager { ...@@ -51,34 +147,34 @@ public class QuestManager extends BasePlayerManager {
return mainQuest.getChildQuests().get(questId); return mainQuest.getChildQuests().get(questId);
} }
public void forEachQuest(Consumer<GameQuest> callback) { public void forEachQuest(Consumer<GameQuest> callback) {
for (GameMainQuest mainQuest : getQuests().values()) { for (GameMainQuest mainQuest : getMainQuests().values()) {
for (GameQuest quest : mainQuest.getChildQuests().values()) { for (GameQuest quest : mainQuest.getChildQuests().values()) {
callback.accept(quest); callback.accept(quest);
} }
} }
} }
public void forEachMainQuest(Consumer<GameMainQuest> callback) { public void forEachMainQuest(Consumer<GameMainQuest> callback) {
for (GameMainQuest mainQuest : getQuests().values()) { for (GameMainQuest mainQuest : getMainQuests().values()) {
callback.accept(mainQuest); callback.accept(mainQuest);
} }
} }
// TODO // TODO
public void forEachActiveQuest(Consumer<GameQuest> callback) { public void forEachActiveQuest(Consumer<GameQuest> callback) {
for (GameMainQuest mainQuest : getQuests().values()) { for (GameMainQuest mainQuest : getMainQuests().values()) {
for (GameQuest quest : mainQuest.getChildQuests().values()) { for (GameQuest quest : mainQuest.getChildQuests().values()) {
if (quest.getState() != QuestState.QUEST_STATE_FINISHED) { if (quest.getState() != QuestState.QUEST_STATE_FINISHED) {
callback.accept(quest); callback.accept(quest);
} }
} }
} }
} }
public GameMainQuest addMainQuest(QuestData questConfig) { public GameMainQuest addMainQuest(QuestData questConfig) {
GameMainQuest mainQuest = new GameMainQuest(getPlayer(), questConfig.getMainId()); GameMainQuest mainQuest = new GameMainQuest(getPlayer(), questConfig.getMainId());
getQuests().put(mainQuest.getParentQuestId(), mainQuest); getMainQuests().put(mainQuest.getParentQuestId(), mainQuest);
getPlayer().sendPacket(new PacketFinishedParentQuestUpdateNotify(mainQuest)); getPlayer().sendPacket(new PacketFinishedParentQuestUpdateNotify(mainQuest));
...@@ -102,18 +198,16 @@ public class QuestManager extends BasePlayerManager { ...@@ -102,18 +198,16 @@ public class QuestManager extends BasePlayerManager {
// Sub quest // Sub quest
GameQuest quest = mainQuest.getChildQuestById(questId); GameQuest quest = mainQuest.getChildQuestById(questId);
if (quest != null) { // Forcefully start
return null; quest.start();
}
// Create
quest = new GameQuest(mainQuest, questConfig);
// Save main quest // Save main quest
mainQuest.save(); mainQuest.save();
// Send packet // Send packet
getPlayer().sendPacket(new PacketQuestListUpdateNotify(quest)); getPlayer().sendPacket(new PacketQuestListUpdateNotify(mainQuest.getChildQuests().values().stream()
.filter(p -> p.getState() != QuestState.QUEST_STATE_UNSTARTED)
.toList()));
return quest; return quest;
} }
...@@ -133,55 +227,81 @@ public class QuestManager extends BasePlayerManager { ...@@ -133,55 +227,81 @@ public class QuestManager extends BasePlayerManager {
triggerEvent(condType, "", params); 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); Grasscutter.getLogger().debug("Trigger Event {}, {}, {}", condType, paramStr, params);
Set<GameQuest> changedQuests = new HashSet<>(); List<GameMainQuest> checkMainQuests = this.getMainQuests().values().stream()
.filter(i -> i.getState() != ParentQuestState.PARENT_QUEST_STATE_FINISHED)
this.forEachActiveQuest(quest -> { .toList();
QuestData data = quest.getData(); switch(condType){
//accept Conds
for (int i = 0; i < data.getFinishCond().size(); i++) { case QUEST_COND_STATE_EQUAL:
if (quest.getFinishProgressList() == null case QUEST_COND_STATE_NOT_EQUAL:
|| quest.getFinishProgressList().length == 0 case QUEST_COND_COMPLETE_TALK:
|| quest.getFinishProgressList()[i] == 1) { case QUEST_COND_LUA_NOTIFY:
continue; 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); //fail Conds
case QUEST_CONTENT_NOT_FINISH_PLOT:
if (condition.getType() != condType) { for (GameMainQuest mainquest : checkMainQuests) {
continue; mainquest.tryFailSubQuests(condType, paramStr, params);
} }
break;
boolean result = getPlayer().getServer().getQuestSystem().triggerContent(quest, condition, paramStr, params); //finish Conds
case QUEST_CONTENT_COMPLETE_TALK:
if (result) { case QUEST_CONTENT_FINISH_PLOT:
quest.getFinishProgressList()[i] = 1; case QUEST_CONTENT_COMPLETE_ANY_TALK:
case QUEST_CONTENT_LUA_NOTIFY:
changedQuests.add(quest); 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;
});
//finish Or Fail Conds
for (GameQuest quest : changedQuests) { case QUEST_CONTENT_GAME_TIME_TICK:
LogicType logicType = quest.getData().getFailCondComb(); case QUEST_CONTENT_QUEST_STATE_EQUAL:
int[] progress = quest.getFinishProgressList(); case QUEST_CONTENT_ADD_QUEST_PROGRESS:
case QUEST_CONTENT_LEAVE_SCENE:
// Handle logical comb for (GameMainQuest mainQuest : checkMainQuests) {
boolean finish = LogicType.calculate(logicType, progress); mainQuest.tryFailSubQuests(condType, paramStr, params);
mainQuest.tryFinishSubQuests(condType, paramStr, params);
// Finish }
if (finish) { break;
quest.finish(); //QUEST_EXEC are handled directly by each subQuest
} else {
getPlayer().sendPacket(new PacketQuestProgressUpdateNotify(quest)); //Unused
quest.save(); 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<QuestGroupSuite> getSceneGroupSuite(int sceneId) { public List<QuestGroupSuite> getSceneGroupSuite(int sceneId) {
return getQuests().values().stream() return getMainQuests().values().stream()
.filter(i -> i.getState() != ParentQuestState.PARENT_QUEST_STATE_FINISHED) .filter(i -> i.getState() != ParentQuestState.PARENT_QUEST_STATE_FINISHED)
.map(GameMainQuest::getQuestGroupSuites) .map(GameMainQuest::getQuestGroupSuites)
.filter(Objects::nonNull) .filter(Objects::nonNull)
...@@ -195,12 +315,16 @@ public class QuestManager extends BasePlayerManager { ...@@ -195,12 +315,16 @@ public class QuestManager extends BasePlayerManager {
for (GameMainQuest mainQuest : quests) { for (GameMainQuest mainQuest : quests) {
mainQuest.setOwner(this.getPlayer()); mainQuest.setOwner(this.getPlayer());
for (GameQuest quest : mainQuest.getChildQuests().values()) { for (GameQuest quest : mainQuest.getChildQuests().values()) {
quest.setMainQuest(mainQuest); quest.setMainQuest(mainQuest);
quest.setConfig(GameData.getQuestDataMap().get(quest.getQuestId())); quest.setConfig(GameData.getQuestDataMap().get(quest.getSubQuestId()));
} }
this.getQuests().put(mainQuest.getParentQuestId(), mainQuest); this.getMainQuests().put(mainQuest.getParentQuestId(), mainQuest);
} }
}
public List<GameMainQuest> getActiveMainQuests() {
return getMainQuests().values().stream().filter(p -> !p.isFinished()).toList();
} }
} }
...@@ -8,7 +8,7 @@ import emu.grasscutter.game.quest.handlers.QuestBaseHandler; ...@@ -8,7 +8,7 @@ import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
@QuestValue(QuestTrigger.QUEST_COND_LUA_NOTIFY) @QuestValue(QuestTrigger.QUEST_COND_LUA_NOTIFY)
public class ConditionLuaNotify extends QuestBaseHandler { public class ConditionLuaNotify extends QuestBaseHandler {
//Wrong implementation. Example: 7010226 has no paramStr
@Override @Override
public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) { public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) {
return condition.getParam()[0] == Integer.parseInt(paramStr); return condition.getParam()[0] == Integer.parseInt(paramStr);
......
package emu.grasscutter.game.quest.conditions; package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.quest.QuestValue; import emu.grasscutter.game.quest.QuestValue;
import emu.grasscutter.data.excels.QuestData.QuestCondition; import emu.grasscutter.data.excels.QuestData.QuestCondition;
import emu.grasscutter.game.quest.GameQuest; import emu.grasscutter.game.quest.GameQuest;
...@@ -11,13 +12,16 @@ public class ConditionStateEqual extends QuestBaseHandler { ...@@ -11,13 +12,16 @@ public class ConditionStateEqual extends QuestBaseHandler {
@Override @Override
public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) { 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 checkQuest.getState().getValue() == condition.getParam()[1];
return false;
} }
} }
...@@ -11,7 +11,12 @@ public class ContentAddQuestProgress extends QuestBaseHandler { ...@@ -11,7 +11,12 @@ public class ContentAddQuestProgress extends QuestBaseHandler {
@Override @Override
public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) { 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
} }
} }
package emu.grasscutter.game.quest.content; 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.game.quest.QuestValue;
import emu.grasscutter.data.excels.QuestData.QuestCondition; 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.enums.QuestTrigger;
import emu.grasscutter.game.quest.handlers.QuestBaseHandler; import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
...@@ -11,7 +13,9 @@ public class ContentCompleteTalk extends QuestBaseHandler { ...@@ -11,7 +13,9 @@ public class ContentCompleteTalk extends QuestBaseHandler {
@Override @Override
public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) { 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;
} }
} }
...@@ -11,7 +11,7 @@ public class ContentEnterDungeon extends QuestBaseHandler { ...@@ -11,7 +11,7 @@ public class ContentEnterDungeon extends QuestBaseHandler {
@Override @Override
public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) { 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]
} }
} }
package emu.grasscutter.game.quest.content; 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.game.quest.QuestValue;
import emu.grasscutter.data.excels.QuestData.QuestCondition; import emu.grasscutter.data.excels.QuestData.QuestCondition;
import emu.grasscutter.game.quest.GameQuest; import emu.grasscutter.game.quest.GameQuest;
...@@ -11,7 +13,9 @@ public class ContentFinishPlot extends QuestBaseHandler { ...@@ -11,7 +13,9 @@ public class ContentFinishPlot extends QuestBaseHandler {
@Override @Override
public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) { 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;
}
} }
...@@ -11,13 +11,9 @@ public class ContentQuestStateEqual extends QuestBaseHandler { ...@@ -11,13 +11,9 @@ public class ContentQuestStateEqual extends QuestBaseHandler {
@Override @Override
public boolean execute(GameQuest quest, QuestCondition condition, String paramStr, int... params) { 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) {return false;}
if (checkQuest != null) { return checkQuest.getState().getValue() == params[1];
return checkQuest.getState().getValue() == params[1];
}
return false;
} }
} }
...@@ -5,10 +5,17 @@ public enum QuestState { ...@@ -5,10 +5,17 @@ public enum QuestState {
QUEST_STATE_UNSTARTED (1), QUEST_STATE_UNSTARTED (1),
QUEST_STATE_UNFINISHED (2), QUEST_STATE_UNFINISHED (2),
QUEST_STATE_FINISHED (3), 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; private final int value;
QuestState(int id) { QuestState(int id) {
this.value = id; this.value = id;
} }
......
...@@ -92,6 +92,7 @@ public class SceneScriptManager { ...@@ -92,6 +92,7 @@ public class SceneScriptManager {
} }
public void registerTrigger(SceneTrigger trigger) { public void registerTrigger(SceneTrigger trigger) {
getTriggersByEvent(trigger.event).add(trigger); getTriggersByEvent(trigger.event).add(trigger);
Grasscutter.getLogger().debug("Registered trigger {}", trigger.name);
} }
public void deregisterTrigger(List<SceneTrigger> triggers) { public void deregisterTrigger(List<SceneTrigger> triggers) {
triggers.forEach(this::deregisterTrigger); triggers.forEach(this::deregisterTrigger);
...@@ -122,6 +123,7 @@ public class SceneScriptManager { ...@@ -122,6 +123,7 @@ public class SceneScriptManager {
public void registerRegion(EntityRegion region) { public void registerRegion(EntityRegion region) {
regions.put(region.getId(), 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){ public void registerRegionInGroupSuite(SceneGroup group, SceneSuite suite){
suite.sceneRegions.stream().map(region -> new EntityRegion(this.getScene(), region)) suite.sceneRegions.stream().map(region -> new EntityRegion(this.getScene(), region))
...@@ -195,9 +197,30 @@ public class SceneScriptManager { ...@@ -195,9 +197,30 @@ public class SceneScriptManager {
.filter(e -> e.getEntityType() == EntityType.Avatar.getValue() && region.getMetaRegion().contains(e.getPosition())) .filter(e -> e.getEntityType() == EntityType.Avatar.getValue() && region.getMetaRegion().contains(e.getPosition()))
.forEach(region::addEntity); .forEach(region::addEntity);
var players = region.getScene().getPlayers();
int targetID = 0;
if(players.size() > 0)
targetID = players.get(0).getUid();
if (region.hasNewEntities()) { if (region.hasNewEntities()) {
Grasscutter.getLogger().trace("Call EVENT_ENTER_REGION_{}",region.getMetaRegion().config_id);
callEvent(EventType.EVENT_ENTER_REGION, new ScriptArgs(region.getConfigId()) callEvent(EventType.EVENT_ENTER_REGION, new ScriptArgs(region.getConfigId())
.setSourceEntityId(region.getId()) .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()) .setTargetEntityId(region.getFirstEntityId())
); );
...@@ -286,27 +309,39 @@ public class SceneScriptManager { ...@@ -286,27 +309,39 @@ public class SceneScriptManager {
} }
private void realCallEvent(int eventType, ScriptArgs params) { private void realCallEvent(int eventType, ScriptArgs params) {
try{ try {
ScriptLoader.getScriptLib().setSceneScriptManager(this); Set<SceneTrigger> relevantTriggers = new HashSet<>();
for (SceneTrigger trigger : this.getTriggersByEvent(eventType)) { if(eventType == EventType.EVENT_ENTER_REGION || eventType == EventType.EVENT_LEAVE_REGION) {
try{ List<SceneTrigger> relevantTriggersList = this.getTriggersByEvent(eventType).stream()
ScriptLoader.getScriptLib().setCurrentGroup(trigger.currentGroup); .filter(p -> p.condition.contains(String.valueOf(params.param1))).toList();
relevantTriggers = new HashSet<>(relevantTriggersList);
LuaValue ret = callScriptFunc(trigger.condition, trigger.currentGroup, params); } else {relevantTriggers = this.getTriggersByEvent(eventType);}
Grasscutter.getLogger().trace("Call Condition Trigger {}", trigger.condition); for (SceneTrigger trigger : relevantTriggers) {
try {
if (ret.isboolean() && ret.checkboolean()) { ScriptLoader.getScriptLib().setCurrentGroup(trigger.currentGroup);
// the SetGroupVariableValueByGroup in tower need the param to record the first stage time LuaValue ret = this.callScriptFunc(trigger.condition, trigger.currentGroup, params);
callScriptFunc(trigger.action, trigger.currentGroup, params); Grasscutter.getLogger().trace("Call Condition Trigger {}, [{},{},{}]", trigger.condition, params.param1, params.source_eid, params.target_eid);
Grasscutter.getLogger().trace("Call Action Trigger {}", trigger.action); if (ret.isboolean() && ret.checkboolean()) {
} // the SetGroupVariableValueByGroup in tower need the param to record the first stage time
//TODO some ret may not bool this.callScriptFunc(trigger.action, trigger.currentGroup, params);
Grasscutter.getLogger().trace("Call Action Trigger {}", trigger.action);
}finally { if (trigger.event == EventType.EVENT_ENTER_REGION) {
ScriptLoader.getScriptLib().removeCurrentGroup(); EntityRegion region = this.regions.values().stream().filter(p -> p.getConfigId() == params.param1).toList().get(0);
} getScene().getPlayers().forEach(p -> p.onEnterRegion(region.getMetaRegion()));
} } else if (trigger.event == EventType.EVENT_LEAVE_REGION) {
}finally { EntityRegion region = this.regions.values().stream().filter(p -> p.getConfigId() == params.param1).toList().get(0);
getScene().getPlayers().forEach(p -> p.onLeaveRegion(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 // make sure it is removed
ScriptLoader.getScriptLib().removeSceneScriptManager(); ScriptLoader.getScriptLib().removeSceneScriptManager();
} }
......
...@@ -26,7 +26,8 @@ public class SceneRegion { ...@@ -26,7 +26,8 @@ public class SceneRegion {
var x = Math.pow(pos.getX() - position.getX(), 2); var x = Math.pow(pos.getX() - position.getX(), 2);
var y = Math.pow(pos.getY() - position.getY(), 2); var y = Math.pow(pos.getY() - position.getY(), 2);
var z = Math.pow(pos.getZ() - position.getZ(), 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; return false;
} }
......
...@@ -29,22 +29,70 @@ public class LuaSerializer implements Serializer { ...@@ -29,22 +29,70 @@ public class LuaSerializer implements Serializer {
public <T> T toObject(Class<T> type, Object obj) { public <T> T toObject(Class<T> type, Object obj) {
return serialize(type, (LuaTable) obj); return serialize(type, (LuaTable) obj);
} }
public <T> List<T> serializeList(Class<T> type, LuaTable table) { @Override
public <T> Map<String, T> toMap(Class<T> type, Object obj) {
return serializeMap(type, (LuaTable) obj);
}
private <T> Map<String,T> serializeMap(Class<T> type, LuaTable table) {
Map<String,T> 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 <T> List<T> serializeList(Class<T> type, LuaTable table) {
List<T> list = new ArrayList<>(); List<T> list = new ArrayList<>();
if (table == null) { if (table == null) {
return list; return list;
} }
try { try {
LuaValue[] keys = table.keys(); LuaValue[] keys = table.keys();
for (LuaValue k : keys) { for (LuaValue k : keys) {
try { try {
LuaValue keyValue = table.get(k); LuaValue keyValue = table.get(k);
T object = null; T object = null;
if (keyValue.istable()) { if (keyValue.istable()) {
object = serialize(type, keyValue.checktable()); object = serialize(type, keyValue.checktable());
} else if (keyValue.isint()) { } else if (keyValue.isint()) {
...@@ -75,7 +123,7 @@ public class LuaSerializer implements Serializer { ...@@ -75,7 +123,7 @@ public class LuaSerializer implements Serializer {
public <T> T serialize(Class<T> type, LuaTable table) { public <T> T serialize(Class<T> type, LuaTable table) {
T object = null; T object = null;
if (type == List.class) { if (type == List.class) {
try { try {
Class<T> listType = (Class<T>) type.getTypeParameters()[0].getClass(); Class<T> listType = (Class<T>) type.getTypeParameters()[0].getClass();
...@@ -85,7 +133,7 @@ public class LuaSerializer implements Serializer { ...@@ -85,7 +133,7 @@ public class LuaSerializer implements Serializer {
return null; return null;
} }
} }
try { try {
if (!methodAccessCache.containsKey(type)) { if (!methodAccessCache.containsKey(type)) {
cacheType(type); cacheType(type);
...@@ -98,7 +146,7 @@ public class LuaSerializer implements Serializer { ...@@ -98,7 +146,7 @@ public class LuaSerializer implements Serializer {
if (table == null) { if (table == null) {
return object; return object;
} }
LuaValue[] keys = table.keys(); LuaValue[] keys = table.keys();
for (LuaValue k : keys) { for (LuaValue k : keys) {
try { try {
...@@ -131,7 +179,7 @@ public class LuaSerializer implements Serializer { ...@@ -131,7 +179,7 @@ public class LuaSerializer implements Serializer {
Grasscutter.getLogger().info(ScriptUtils.toMap(table).toString()); Grasscutter.getLogger().info(ScriptUtils.toMap(table).toString());
e.printStackTrace(); e.printStackTrace();
} }
return object; return object;
} }
......
package emu.grasscutter.scripts.serializer; package emu.grasscutter.scripts.serializer;
import java.util.List; import java.util.List;
import java.util.Map;
import org.luaj.vm2.LuaTable;
public interface Serializer { public interface Serializer {
public <T> List<T> toList(Class<T> type, Object obj); public <T> List<T> toList(Class<T> type, Object obj);
public <T> T toObject(Class<T> type, Object obj); public <T> T toObject(Class<T> type, Object obj);
public <T> Map<String,T> toMap(Class<T> type, Object obj);
} }
package emu.grasscutter.server.packet.recv; 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.game.quest.enums.QuestTrigger;
import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.Opcodes;
import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketHandler;
...@@ -7,6 +10,9 @@ import emu.grasscutter.net.packet.PacketOpcodes; ...@@ -7,6 +10,9 @@ import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.net.proto.AddQuestContentProgressReqOuterClass; import emu.grasscutter.net.proto.AddQuestContentProgressReqOuterClass;
import emu.grasscutter.server.game.GameSession; import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.server.packet.send.PacketAddQuestContentProgressRsp; 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) @Opcodes(PacketOpcodes.AddQuestContentProgressReq)
public class HandlerAddQuestContentProgressReq extends PacketHandler { public class HandlerAddQuestContentProgressReq extends PacketHandler {
...@@ -14,9 +20,14 @@ public class HandlerAddQuestContentProgressReq extends PacketHandler { ...@@ -14,9 +20,14 @@ public class HandlerAddQuestContentProgressReq extends PacketHandler {
@Override @Override
public void handle(GameSession session, byte[] header, byte[] payload) throws Exception { public void handle(GameSession session, byte[] header, byte[] payload) throws Exception {
var req = AddQuestContentProgressReqOuterClass.AddQuestContentProgressReq.parseFrom(payload); var req = AddQuestContentProgressReqOuterClass.AddQuestContentProgressReq.parseFrom(payload);
//Find all conditions in quest that are the same as the given one
session.getPlayer().getQuestManager().triggerEvent(QuestTrigger.getContentTriggerByValue(req.getContentType()), req.getParam()); Stream<QuestCondition> finishCond = GameData.getQuestDataMap().get(req.getParam()).getFinishCond().stream();
Stream<QuestCondition> acceptCond = GameData.getQuestDataMap().get(req.getParam()).getAcceptCond().stream();
Stream<QuestCondition> failCond = GameData.getQuestDataMap().get(req.getParam()).getFailCond().stream();
List<QuestCondition> 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())); session.send(new PacketAddQuestContentProgressRsp(req.getContentType()));
} }
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment