Commit f4770cf2 authored by Melledy's avatar Melledy Committed by GitHub
Browse files

Merge pull request #816 from Grasscutters/dev-quests

Implement quests
parents 891c70e5 cbd46e92
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.GameQuest;
import java.util.List;
import static emu.grasscutter.utils.Language.translate;
@Command(label = "quest", usage = "quest <add|finish> [quest id]", permission = "player.quest", permissionTargeted = "player.quest.others", description = "commands.quest.description")
public final class QuestCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (targetPlayer == null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.execution.need_target"));
return;
}
if (args.size() != 2) {
CommandHandler.sendMessage(sender, translate(sender, "commands.quest.usage"));
return;
}
String cmd = args.get(0).toLowerCase();
int questId;
try {
questId = Integer.parseInt(args.get(1));
} catch (Exception e) {
CommandHandler.sendMessage(sender, translate(sender, "commands.quest.invalid_id"));
return;
}
switch (cmd) {
case "add" -> {
GameQuest quest = sender.getQuestManager().addQuest(questId);
if (quest != null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.quest.added", questId));
return;
}
CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found"));
}
case "finish" -> {
GameQuest quest = sender.getQuestManager().getQuestById(questId);
if (quest == null) {
CommandHandler.sendMessage(sender, translate(sender, "commands.quest.not_found"));
return;
}
quest.finish();
CommandHandler.sendMessage(sender, translate(sender, "commands.quest.finished", questId));
}
default -> {
CommandHandler.sendMessage(sender, translate(sender, "commands.quest.usage"));
}
}
}
}
......@@ -12,6 +12,7 @@ import emu.grasscutter.data.custom.AbilityEmbryoEntry;
import emu.grasscutter.data.custom.AbilityModifier;
import emu.grasscutter.data.custom.AbilityModifierEntry;
import emu.grasscutter.data.custom.OpenConfigEntry;
import emu.grasscutter.data.custom.MainQuestData;
import emu.grasscutter.data.custom.ScenePointEntry;
import emu.grasscutter.data.def.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
......@@ -27,6 +28,7 @@ public class GameData {
private static final Map<String, AbilityModifierEntry> abilityModifiers = new HashMap<>();
private static final Map<String, OpenConfigEntry> openConfigEntries = new HashMap<>();
private static final Map<String, ScenePointEntry> scenePointEntries = new HashMap<>();
private static final Int2ObjectMap<MainQuestData> mainQuestData = new Int2ObjectOpenHashMap<>();
// ExcelConfigs
private static final Int2ObjectMap<PlayerLevelData> playerLevelDataMap = new Int2ObjectOpenHashMap<>();
......@@ -68,6 +70,7 @@ public class GameData {
private static final Int2ObjectMap<WorldLevelData> worldLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<DailyDungeonData> dailyDungeonDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<DungeonData> dungeonDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<QuestData> questDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ShopGoodsData> shopGoodsDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CombineData> combineDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<RewardPreviewData> rewardPreviewDataMap = new Int2ObjectOpenHashMap<>();
......@@ -122,6 +125,10 @@ public class GameData {
return getScenePointEntries().get(sceneId + "_" + pointId);
}
public static Int2ObjectMap<MainQuestData> getMainQuestDataMap() {
return mainQuestData;
}
public static Int2ObjectMap<AvatarData> getAvatarDataMap() {
return avatarDataMap;
}
......@@ -331,4 +338,8 @@ public class GameData {
public static Int2ObjectMap<TowerScheduleData> getTowerScheduleDataMap(){
return towerScheduleDataMap;
}
public static Int2ObjectMap<QuestData> getQuestDataMap() {
return questDataMap;
}
}
......@@ -24,6 +24,7 @@ import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.data.custom.AbilityModifier.AbilityModifierActionType;
import emu.grasscutter.data.custom.AbilityModifierEntry;
import emu.grasscutter.data.custom.OpenConfigEntry;
import emu.grasscutter.data.custom.MainQuestData;
import emu.grasscutter.data.custom.ScenePointEntry;
import emu.grasscutter.game.world.SpawnDataEntry.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
......@@ -58,8 +59,9 @@ public class ResourceLoader {
loadResources();
// Process into depots
GameDepot.load();
// Load spawn data
// Load spawn data and quests
loadSpawnData();
loadQuests();
// Load scene points - must be done AFTER resources are loaded
loadScenePoints();
// Custom - TODO move this somewhere else
......@@ -395,6 +397,29 @@ public class ResourceLoader {
}
}
private static void loadQuests() {
File folder = new File(RESOURCE("BinOutput/Quest/"));
if (!folder.exists()) {
return;
}
for (File file : folder.listFiles()) {
MainQuestData mainQuest = null;
try (FileReader fileReader = new FileReader(file)) {
mainQuest = Grasscutter.getGsonFactory().fromJson(fileReader, MainQuestData.class);
} catch (Exception e) {
e.printStackTrace();
continue;
}
GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest);
}
Grasscutter.getLogger().info("Loaded " + GameData.getMainQuestDataMap().size() + " MainQuestDatas.");
}
// BinOutput configs
private static class AvatarConfig {
......
package emu.grasscutter.data.custom;
import emu.grasscutter.game.quest.enums.LogicType;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.quest.enums.QuestType;
public class MainQuestData {
private int id;
private int series;
private QuestType type;
private long titleTextMapHash;
private int[] suggestTrackMainQuestList;
private int[] rewardIdList;
private SubQuestData[] subQuests;
public int getId() {
return id;
}
public int getSeries() {
return series;
}
public QuestType getType() {
return type;
}
public long getTitleTextMapHash() {
return titleTextMapHash;
}
public int[] getSuggestTrackMainQuestList() {
return suggestTrackMainQuestList;
}
public int[] getRewardIdList() {
return rewardIdList;
}
public SubQuestData[] getSubQuests() {
return subQuests;
}
public static class SubQuestData {
private int subId;
public int getSubId() {
return subId;
}
}
}
package emu.grasscutter.data.def;
import java.util.Arrays;
import java.util.List;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import emu.grasscutter.game.quest.enums.LogicType;
import emu.grasscutter.game.quest.enums.QuestTrigger;
@ResourceType(name = "QuestExcelConfigData.json")
public class QuestData extends GameResource {
private int SubId;
private int MainId;
private int Order;
private long DescTextMapHash;
private LogicType AcceptCondComb;
private QuestCondition[] acceptConditons;
private LogicType FinishCondComb;
private QuestCondition[] finishConditons;
private LogicType FailCondComb;
private QuestCondition[] failConditons;
private List<QuestParam> AcceptCond;
private List<QuestParam> FinishCond;
private List<QuestParam> FailCond;
private List<QuestExecParam> BeginExec;
private List<QuestExecParam> FinishExec;
private List<QuestExecParam> FailExec;
public int getId() {
return SubId;
}
public int getMainId() {
return MainId;
}
public int getOrder() {
return Order;
}
public long getDescTextMapHash() {
return DescTextMapHash;
}
public LogicType getAcceptCondComb() {
return AcceptCondComb;
}
public QuestCondition[] getAcceptCond() {
return acceptConditons;
}
public LogicType getFinishCondComb() {
return FinishCondComb;
}
public QuestCondition[] getFinishCond() {
return finishConditons;
}
public LogicType getFailCondComb() {
return FailCondComb;
}
public QuestCondition[] getFailCond() {
return failConditons;
}
public void onLoad() {
this.acceptConditons = AcceptCond.stream().filter(p -> p.Type != null).map(QuestCondition::new).toArray(QuestCondition[]::new);
AcceptCond = null;
this.finishConditons = FinishCond.stream().filter(p -> p.Type != null).map(QuestCondition::new).toArray(QuestCondition[]::new);
FinishCond = null;
this.failConditons = FailCond.stream().filter(p -> p.Type != null).map(QuestCondition::new).toArray(QuestCondition[]::new);
FailCond = null;
}
public class QuestParam {
QuestTrigger Type;
int[] Param;
String count;
}
public class QuestExecParam {
QuestTrigger Type;
String[] Param;
String count;
}
public static class QuestCondition {
private QuestTrigger type;
private int[] param;
private String count;
public QuestCondition(QuestParam param) {
this.type = param.Type;
this.param = param.Param;
}
public QuestTrigger getType() {
return type;
}
public int[] getParam() {
return param;
}
public String getCount() {
return count;
}
}
}
......@@ -15,6 +15,7 @@ import emu.grasscutter.game.gacha.GachaRecord;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.mail.Mail;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.GameMainQuest;
import static com.mongodb.client.model.Filters.eq;
......@@ -111,6 +112,8 @@ public final class DatabaseHelper {
DatabaseManager.getGameDatabase().getCollection("gachas").deleteMany(eq("ownerId", target.getPlayerUid()));
// Delete GameItem.class data
DatabaseManager.getGameDatabase().getCollection("items").deleteMany(eq("ownerId", target.getPlayerUid()));
// Delete GameMainQuest.class data
DatabaseManager.getGameDatabase().getCollection("quests").deleteMany(eq("ownerUid", target.getPlayerUid()));
// Delete friendships.
// Here, we need to make sure to not only delete the deleted account's friendships,
......@@ -260,4 +263,16 @@ public final class DatabaseHelper {
DeleteResult result = DatabaseManager.getGameDatastore().delete(mail);
return result.wasAcknowledged();
}
public static List<GameMainQuest> getAllQuests(Player player) {
return DatabaseManager.getGameDatastore().find(GameMainQuest.class).filter(Filters.eq("ownerUid", player.getUid())).stream().toList();
}
public static void saveQuest(GameMainQuest quest) {
DatabaseManager.getGameDatastore().save(quest);
}
public static boolean deleteQuest(GameMainQuest quest) {
return DatabaseManager.getGameDatastore().delete(quest).wasAcknowledged();
}
}
......@@ -19,6 +19,8 @@ import emu.grasscutter.game.gacha.GachaRecord;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.mail.Mail;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.GameMainQuest;
import emu.grasscutter.game.quest.GameQuest;
import static emu.grasscutter.Configuration.*;
......@@ -27,7 +29,8 @@ public final class DatabaseManager {
private static Datastore dispatchDatastore;
private static final Class<?>[] mappedClasses = new Class<?>[] {
DatabaseCounter.class, Account.class, Player.class, Avatar.class, GameItem.class, Friendship.class, GachaRecord.class, Mail.class
DatabaseCounter.class, Account.class, Player.class, Avatar.class, GameItem.class, Friendship.class,
GachaRecord.class, Mail.class, GameMainQuest.class
};
public static Datastore getGameDatastore() {
......
......@@ -7,6 +7,7 @@ import emu.grasscutter.data.custom.ScenePointEntry;
import emu.grasscutter.data.def.DungeonData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.server.game.GameServer;
......@@ -51,8 +52,9 @@ public class DungeonManager {
int sceneId = data.getSceneId();
player.getScene().setPrevScene(sceneId);
if(player.getWorld().transferPlayerToScene(player, sceneId, data)){
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver);
player.getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON, data.getId());
}
player.getScene().setPrevScenePoint(pointId);
......
......@@ -29,6 +29,9 @@ import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.EntityType;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.quest.GameMainQuest;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestManager;
import emu.grasscutter.game.shop.ShopLimit;
import emu.grasscutter.game.managers.MapMarkManager.*;
import emu.grasscutter.game.tower.TowerManager;
......@@ -95,6 +98,7 @@ public class Player {
@Transient private MailHandler mailHandler;
@Transient private MessageHandler messageHandler;
@Transient private AbilityManager abilityManager;
@Transient private QuestManager questManager;
@Transient private SotSManager sotsManager;
......@@ -150,6 +154,7 @@ public class Player {
this.mailHandler = new MailHandler(this);
this.towerManager = new TowerManager(this);
this.abilityManager = new AbilityManager(this);
this.setQuestManager(new QuestManager(this));
this.pos = new Position();
this.rotation = new Position();
this.properties = new HashMap<>();
......@@ -422,6 +427,14 @@ public class Player {
return towerManager;
}
public QuestManager getQuestManager() {
return questManager;
}
public void setQuestManager(QuestManager questManager) {
this.questManager = questManager;
}
public PlayerGachaInfo getGachaInfo() {
return gachaInfo;
}
......@@ -896,10 +909,8 @@ public class Player {
}
public void sendPacket(BasePacket packet) {
if (this.hasSentAvatarDataNotify) {
this.getSession().send(packet);
}
}
public OnlinePlayerInfo getOnlinePlayerInfo() {
OnlinePlayerInfo.Builder onlineInfo = OnlinePlayerInfo.newBuilder()
......@@ -1135,6 +1146,22 @@ public class Player {
this.getFriendsList().loadFromDatabase();
this.getMailHandler().loadFromDatabase();
this.getQuestManager().loadFromDatabase();
// Quest - Commented out because a problem is caused if you log out while this quest is active
/*
if (getQuestManager().getMainQuestById(351) == null) {
GameQuest quest = getQuestManager().addQuest(35104);
if (quest != null) {
quest.finish();
}
getQuestManager().addQuest(35101);
this.setSceneId(3);
this.getPos().set(GameConstants.START_POSITION);
}
*/
// Create world
World world = new World(this);
......@@ -1155,7 +1182,9 @@ public class Player {
session.send(new PacketStoreWeightLimitNotify());
session.send(new PacketPlayerStoreNotify(this));
session.send(new PacketAvatarDataNotify(this));
session.send(new PacketFinishedParentQuestNotify(this));
session.send(new PacketQuestListNotify(this));
session.send(new PacketServerCondMeetQuestListUpdateNotify(this));
session.send(new PacketAllWidgetDataNotify(this));
session.send(new PacketWidgetGadgetAllDataNotify());
......
package emu.grasscutter.game.quest;
import java.util.HashMap;
import java.util.Map;
import org.bson.types.ObjectId;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.enums.ParentQuestState;
import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.net.proto.ChildQuestOuterClass.ChildQuest;
import emu.grasscutter.net.proto.ParentQuestOuterClass.ParentQuest;
import emu.grasscutter.net.proto.QuestOuterClass.Quest;
import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify;
import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify;
import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@Entity(value = "quests", useDiscriminator = false)
public class GameMainQuest {
@Id private ObjectId id;
@Indexed private int ownerUid;
@Transient private Player owner;
private Map<Integer, GameQuest> childQuests;
private int parentQuestId;
private int[] questVars;
private ParentQuestState state;
private boolean isFinished;
@Deprecated // Morphia only. Do not use.
public GameMainQuest() {}
public GameMainQuest(Player player, int parentQuestId) {
this.owner = player;
this.ownerUid = player.getUid();
this.parentQuestId = parentQuestId;
this.childQuests = new HashMap<>();
this.questVars = new int[5];
this.state = ParentQuestState.PARENT_QUEST_STATE_NONE;
}
public int getParentQuestId() {
return parentQuestId;
}
public int getOwnerUid() {
return ownerUid;
}
public Player getOwner() {
return owner;
}
public void setOwner(Player player) {
if (player.getUid() != this.getOwnerUid()) return;
this.owner = player;
}
public Map<Integer, GameQuest> getChildQuests() {
return childQuests;
}
public GameQuest getChildQuestById(int id) {
return this.getChildQuests().get(id);
}
public int[] getQuestVars() {
return questVars;
}
public ParentQuestState getState() {
return state;
}
public boolean isFinished() {
return isFinished;
}
public void finish() {
this.isFinished = true;
this.state = ParentQuestState.PARENT_QUEST_STATE_FINISHED;
this.getOwner().getSession().send(new PacketFinishedParentQuestUpdateNotify(this));
}
public void save() {
DatabaseHelper.saveQuest(this);
}
public ParentQuest toProto() {
ParentQuest.Builder proto = ParentQuest.newBuilder()
.setParentQuestId(getParentQuestId())
.setIsFinished(isFinished())
.setParentQuestState(getState().getValue());
for (GameQuest quest : this.getChildQuests().values()) {
ChildQuest childQuest = ChildQuest.newBuilder()
.setQuestId(quest.getQuestId())
.setState(quest.getState().getValue())
.build();
proto.addChildQuestList(childQuest);
}
if (getQuestVars() != null) {
for (int i : getQuestVars()) {
proto.addQuestVar(i);
}
}
return proto.build();
}
}
package emu.grasscutter.game.quest;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.custom.MainQuestData;
import emu.grasscutter.data.custom.MainQuestData.SubQuestData;
import emu.grasscutter.data.def.QuestData;
import emu.grasscutter.data.def.QuestData.QuestCondition;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.enums.LogicType;
import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.net.proto.QuestOuterClass.Quest;
import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify;
import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify;
import emu.grasscutter.utils.Utils;
@Entity
public class GameQuest {
@Transient private GameMainQuest mainQuest;
@Transient private QuestData questData;
private int questId;
private int mainQuestId;
private QuestState state;
private int startTime;
private int acceptTime;
private int finishTime;
private int[] finishProgressList;
private int[] failProgressList;
@Deprecated // Morphia only. Do not use.
public GameQuest() {}
public GameQuest(GameMainQuest mainQuest, QuestData questData) {
this.mainQuest = mainQuest;
this.questId = questData.getId();
this.mainQuestId = questData.getMainId();
this.questData = questData;
this.acceptTime = Utils.getCurrentSeconds();
this.startTime = this.acceptTime;
this.state = QuestState.QUEST_STATE_UNFINISHED;
if (questData.getFinishCond()!= null) {
this.finishProgressList = new int[questData.getFinishCond().length];
}
if (questData.getFailCond() != null) {
this.failProgressList = new int[questData.getFailCond().length];
}
this.mainQuest.getChildQuests().put(this.questId, this);
}
public GameMainQuest getMainQuest() {
return mainQuest;
}
public void setMainQuest(GameMainQuest mainQuest) {
this.mainQuest = mainQuest;
}
public Player getOwner() {
return getMainQuest().getOwner();
}
public int getQuestId() {
return questId;
}
public int getMainQuestId() {
return mainQuestId;
}
public QuestData getData() {
return questData;
}
public void setConfig(QuestData config) {
if (this.getQuestId() != config.getId()) return;
this.questData = config;
}
public QuestState getState() {
return state;
}
public void setState(QuestState state) {
this.state = state;
}
public int getStartTime() {
return startTime;
}
public void setStartTime(int startTime) {
this.startTime = startTime;
}
public int getAcceptTime() {
return acceptTime;
}
public void setAcceptTime(int acceptTime) {
this.acceptTime = acceptTime;
}
public int getFinishTime() {
return finishTime;
}
public void setFinishTime(int finishTime) {
this.finishTime = finishTime;
}
public int[] getFinishProgressList() {
return finishProgressList;
}
public void setFinishProgress(int index, int value) {
finishProgressList[index] = value;
}
public int[] getFailProgressList() {
return failProgressList;
}
public void setFailProgress(int index, int value) {
failProgressList[index] = value;
}
public void finish() {
this.state = QuestState.QUEST_STATE_FINISHED;
this.finishTime = Utils.getCurrentSeconds();
if (this.getFinishProgressList() != null) {
for (int i = 0 ; i < getFinishProgressList().length; i++) {
getFinishProgressList()[i] = 1;
}
}
this.getOwner().getSession().send(new PacketQuestProgressUpdateNotify(this));
this.getOwner().getSession().send(new PacketQuestListUpdateNotify(this));
this.save();
this.tryAcceptQuestLine();
}
public boolean tryAcceptQuestLine() {
try {
MainQuestData questConfig = GameData.getMainQuestDataMap().get(this.getMainQuestId());
for (SubQuestData subQuest : questConfig.getSubQuests()) {
GameQuest quest = getMainQuest().getChildQuestById(subQuest.getSubId());
if (quest == null) {
QuestData questData = GameData.getQuestDataMap().get(subQuest.getSubId());
if (questData == null) {
continue;
}
int[] accept = new int[questData.getAcceptCond().length];
// TODO
for (int i = 0; i < questData.getAcceptCond().length; i++) {
QuestCondition condition = questData.getAcceptCond()[i];
boolean result = getOwner().getServer().getQuestHandler().triggerCondition(this, condition, condition.getParam());
accept[i] = result ? 1 : 0;
}
boolean shouldAccept = LogicType.calculate(questData.getAcceptCondComb(), accept);
if (shouldAccept) {
this.getOwner().getQuestManager().addQuest(questData.getId());
}
}
}
} catch (Exception e) {
}
return false;
}
public void save() {
getMainQuest().save();
}
public Quest toProto() {
Quest.Builder proto = Quest.newBuilder()
.setQuestId(this.getQuestId())
.setState(this.getState().getValue())
.setParentQuestId(this.getMainQuestId())
.setStartTime(this.getStartTime())
.setStartGameTime(438)
.setAcceptTime(this.getAcceptTime());
if (this.getFinishProgressList() != null) {
for (int i : this.getFinishProgressList()) {
proto.addFinishProgressList(i);
}
}
if (this.getFailProgressList() != null) {
for (int i : this.getFailProgressList()) {
proto.addFailProgressList(i);
}
}
return proto.build();
}
}
package emu.grasscutter.game.quest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.def.QuestData;
import emu.grasscutter.data.def.QuestData.QuestCondition;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.quest.enums.LogicType;
import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify;
import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify;
import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify;
import emu.grasscutter.server.packet.send.PacketServerCondMeetQuestListUpdateNotify;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public class QuestManager {
private final Player player;
private final Int2ObjectMap<GameMainQuest> quests;
public QuestManager(Player player) {
this.player = player;
this.quests = new Int2ObjectOpenHashMap<>();
}
public Player getPlayer() {
return player;
}
public Int2ObjectMap<GameMainQuest> getQuests() {
return quests;
}
public GameMainQuest getMainQuestById(int mainQuestId) {
return getQuests().get(mainQuestId);
}
public GameQuest getQuestById(int questId) {
QuestData questConfig = GameData.getQuestDataMap().get(questId);
if (questConfig == null) {
return null;
}
GameMainQuest mainQuest = getQuests().get(questConfig.getMainId());
if (mainQuest == null) {
return null;
}
return mainQuest.getChildQuests().get(questId);
}
public void forEachQuest(Consumer<GameQuest> callback) {
for (GameMainQuest mainQuest : getQuests().values()) {
for (GameQuest quest : mainQuest.getChildQuests().values()) {
callback.accept(quest);
}
}
}
// TODO
public void forEachActiveQuest(Consumer<GameQuest> callback) {
for (GameMainQuest mainQuest : getQuests().values()) {
for (GameQuest quest : mainQuest.getChildQuests().values()) {
if (quest.getState() != QuestState.QUEST_STATE_FINISHED) {
callback.accept(quest);
}
}
}
}
public GameMainQuest addMainQuest(QuestData questConfig) {
GameMainQuest mainQuest = new GameMainQuest(getPlayer(), questConfig.getMainId());
getQuests().put(mainQuest.getParentQuestId(), mainQuest);
getPlayer().sendPacket(new PacketFinishedParentQuestUpdateNotify(mainQuest));
return mainQuest;
}
public GameQuest addQuest(int questId) {
QuestData questConfig = GameData.getQuestDataMap().get(questId);
if (questConfig == null) {
return null;
}
// Main quest
GameMainQuest mainQuest = this.getMainQuestById(questConfig.getMainId());
// Create main quest if it doesnt exist
if (mainQuest == null) {
mainQuest = addMainQuest(questConfig);
}
// Sub quest
GameQuest quest = mainQuest.getChildQuestById(questId);
if (quest != null) {
return null;
}
// Create
quest = new GameQuest(mainQuest, questConfig);
// Save main quest
mainQuest.save();
// Send packet
getPlayer().sendPacket(new PacketServerCondMeetQuestListUpdateNotify(quest));
getPlayer().sendPacket(new PacketQuestListUpdateNotify(quest));
return quest;
}
public void triggerEvent(QuestTrigger condType, int... params) {
Set<GameQuest> changedQuests = new HashSet<>();
this.forEachActiveQuest(quest -> {
QuestData data = quest.getData();
for (int i = 0; i < data.getFinishCond().length; i++) {
if (quest.getFinishProgressList()[i] == 1) {
continue;
}
QuestCondition condition = data.getFinishCond()[i];
if (condition.getType() != condType) {
continue;
}
boolean result = getPlayer().getServer().getQuestHandler().triggerContent(quest, condition, params);
if (result) {
quest.getFinishProgressList()[i] = 1;
changedQuests.add(quest);
}
}
});
for (GameQuest quest : changedQuests) {
LogicType logicType = quest.getData().getFailCondComb();
int[] progress = quest.getFinishProgressList();
// Handle logical comb
boolean finish = LogicType.calculate(logicType, progress);
// Finish
if (finish) {
quest.finish();
} else {
getPlayer().sendPacket(new PacketQuestProgressUpdateNotify(quest));
quest.save();
}
}
}
public void loadFromDatabase() {
List<GameMainQuest> quests = DatabaseHelper.getAllQuests(getPlayer());
for (GameMainQuest mainQuest : quests) {
mainQuest.setOwner(this.getPlayer());
for (GameQuest quest : mainQuest.getChildQuests().values()) {
quest.setMainQuest(mainQuest);
quest.setConfig(GameData.getQuestDataMap().get(quest.getQuestId()));
}
this.getQuests().put(mainQuest.getParentQuestId(), mainQuest);
}
}
}
package emu.grasscutter.game.quest;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import emu.grasscutter.game.quest.enums.QuestTrigger;
@Retention(RetentionPolicy.RUNTIME)
public @interface QuestValue {
QuestTrigger value();
}
package emu.grasscutter.game.quest;
import java.util.Set;
import org.reflections.Reflections;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.def.QuestData.QuestCondition;
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
@SuppressWarnings("unchecked")
public class ServerQuestHandler {
private final Int2ObjectMap<QuestBaseHandler> condHandlers;
private final Int2ObjectMap<QuestBaseHandler> contHandlers;
private final Int2ObjectMap<QuestBaseHandler> execHandlers;
public ServerQuestHandler() {
this.condHandlers = new Int2ObjectOpenHashMap<>();
this.contHandlers = new Int2ObjectOpenHashMap<>();
this.execHandlers = new Int2ObjectOpenHashMap<>();
this.registerHandlers();
}
public void registerHandlers() {
this.registerHandlers(this.condHandlers, "emu.grasscutter.game.quest.conditions");
this.registerHandlers(this.contHandlers, "emu.grasscutter.game.quest.content");
this.registerHandlers(this.execHandlers, "emu.grasscutter.game.quest.exec");
}
public void registerHandlers(Int2ObjectMap<QuestBaseHandler> map, String packageName) {
Reflections reflections = new Reflections(packageName);
Set<?> handlerClasses = reflections.getSubTypesOf(QuestBaseHandler.class);
for (Object obj : handlerClasses) {
this.registerPacketHandler(map, (Class<? extends QuestBaseHandler>) obj);
}
}
public void registerPacketHandler(Int2ObjectMap<QuestBaseHandler> map, Class<? extends QuestBaseHandler> handlerClass) {
try {
QuestValue opcode = handlerClass.getAnnotation(QuestValue.class);
if (opcode == null || opcode.value().getValue() <= 0) {
return;
}
QuestBaseHandler packetHandler = (QuestBaseHandler) handlerClass.newInstance();
map.put(opcode.value().getValue(), packetHandler);
} catch (Exception e) {
e.printStackTrace();
}
}
// TODO make cleaner
public boolean triggerCondition(GameQuest quest, QuestCondition condition, int... params) {
QuestBaseHandler handler = condHandlers.get(condition.getType().getValue());
if (handler == null || quest.getData() == null) {
return false;
}
return handler.execute(quest, condition, params);
}
public boolean triggerContent(GameQuest quest, QuestCondition condition, int... params) {
QuestBaseHandler handler = contHandlers.get(condition.getType().getValue());
if (handler == null || quest.getData() == null) {
return false;
}
return handler.execute(quest, condition, params);
}
public boolean triggerExec(GameQuest quest, QuestCondition condition, int... params) {
QuestBaseHandler handler = execHandlers.get(condition.getType().getValue());
if (handler == null || quest.getData() == null) {
return false;
}
return handler.execute(quest, condition, params);
}
}
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.def.QuestData.QuestCondition;
import emu.grasscutter.game.quest.QuestValue;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
@QuestValue(QuestTrigger.QUEST_CONTENT_NONE)
public class BaseCondition extends QuestBaseHandler {
@Override
public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
// TODO Auto-generated method stub
return false;
}
}
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.def.QuestData.QuestCondition;
import emu.grasscutter.game.quest.QuestValue;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
@QuestValue(QuestTrigger.QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER)
public class ConditionPlayerLevelEqualGreater extends QuestBaseHandler {
@Override
public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
return quest.getOwner().getLevel() >= params[0];
}
}
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.def.QuestData.QuestCondition;
import emu.grasscutter.game.quest.QuestValue;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
@QuestValue(QuestTrigger.QUEST_COND_STATE_EQUAL)
public class ConditionStateEqual extends QuestBaseHandler {
@Override
public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
GameQuest checkQuest = quest.getOwner().getQuestManager().getQuestById(params[0]);
if (checkQuest != null) {
return checkQuest.getState().getValue() == params[1];
}
return false;
}
}
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.def.QuestData.QuestCondition;
import emu.grasscutter.game.quest.QuestValue;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
@QuestValue(QuestTrigger.QUEST_CONTENT_NONE)
public class BaseContent extends QuestBaseHandler {
@Override
public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
// TODO Auto-generated method stub
return false;
}
}
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.def.QuestData.QuestCondition;
import emu.grasscutter.game.quest.QuestValue;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
@QuestValue(QuestTrigger.QUEST_CONTENT_COMPLETE_TALK)
public class ContentCompleteTalk extends QuestBaseHandler {
@Override
public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
return condition.getParam()[0] == params[0];
}
}
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.def.QuestData.QuestCondition;
import emu.grasscutter.game.quest.QuestValue;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
@QuestValue(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON)
public class ContentEnterDungeon extends QuestBaseHandler {
@Override
public boolean execute(GameQuest quest, QuestCondition condition, int... params) {
return condition.getParam()[0] == params[0];
}
}
Markdown is supported
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