Commit 1de402bd authored by KingRainbow44's avatar KingRainbow44
Browse files

Merge branch 'development' into more-events

parents b9b4b6f4 f25fb629
...@@ -23,7 +23,7 @@ def ppprint(data): ...@@ -23,7 +23,7 @@ def ppprint(data):
class JsonHelpers: class JsonHelpers:
@staticmethod @staticmethod
def load(filename: str) -> dict: def load(filename: str) -> dict:
with open(filename, 'r') as file: with open(filename, 'r', encoding='utf-8') as file:
return json.load(file) return json.load(file)
@staticmethod @staticmethod
...@@ -117,7 +117,7 @@ class LanguageManager: ...@@ -117,7 +117,7 @@ class LanguageManager:
for file in files: for file in files:
if file.rpartition('.')[-1] in SOURCE_EXTENSIONS: if file.rpartition('.')[-1] in SOURCE_EXTENSIONS:
filename = os.path.join(root, file) filename = os.path.join(root, file)
with open(filename, 'r') as f: with open(filename, 'r', encoding='utf-8') as f:
data = f.read() # Loads in entire file at once data = f.read() # Loads in entire file at once
for k in self.TRANSLATION_KEY.findall(data): for k in self.TRANSLATION_KEY.findall(data):
used.add(k) used.add(k)
......
...@@ -28,7 +28,7 @@ public interface CommandHandler { ...@@ -28,7 +28,7 @@ public interface CommandHandler {
if (player == null) { if (player == null) {
Grasscutter.getLogger().info(event.getMessage()); Grasscutter.getLogger().info(event.getMessage());
} else { } else {
player.dropMessage(event.getMessage()); player.dropMessage(event.getMessage().replace("\n\t", "\n\n"));
} }
} }
...@@ -44,6 +44,9 @@ public interface CommandHandler { ...@@ -44,6 +44,9 @@ public interface CommandHandler {
if (alias.length() < command.length()) if (alias.length() < command.length())
command = alias; command = alias;
} }
if (player != null) {
command = "/" + command;
}
String target = switch (annotation.targetRequirement()) { String target = switch (annotation.targetRequirement()) {
case NONE -> ""; case NONE -> "";
case OFFLINE -> "@<UID> "; // TODO: make translation keys for offline and online players case OFFLINE -> "@<UID> "; // TODO: make translation keys for offline and online players
......
...@@ -240,7 +240,7 @@ public final class GiveCommand implements CommandHandler { ...@@ -240,7 +240,7 @@ public final class GiveCommand implements CommandHandler {
} }
private static Avatar makeAvatar(GiveItemParameters param) { private static Avatar makeAvatar(GiveItemParameters param) {
return makeAvatar(param.avatarData, param.lvl, Avatar.getMinPromoteLevel(param.lvl), 0); return makeAvatar(param.avatarData, param.lvl, Avatar.getMinPromoteLevel(param.lvl), param.constellation);
} }
private static Avatar makeAvatar(AvatarData avatarData, int level, int promoteLevel, int constellation) { private static Avatar makeAvatar(AvatarData avatarData, int level, int promoteLevel, int constellation) {
......
package emu.grasscutter.command.commands;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AvatarTalentData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.World;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import java.util.List;
import java.util.Set;
@Command(
label = "setConst",
aliases = {"setconstellation"},
usage = {"<constellation level>"},
permission = "player.setconstellation",
permissionTargeted = "player.setconstellation.others")
public final class SetConstCommand implements CommandHandler {
@Override
public void execute(Player sender, Player targetPlayer, List<String> args) {
if (args.size() < 1) {
sendUsageMessage(sender);
return;
}
try {
int constLevel = Integer.parseInt(args.get(0));
if (constLevel < 0 || constLevel > 6) {
CommandHandler.sendTranslatedMessage(sender, "commands.setConst.range_error");
return;
}
EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity();
if (entity == null) return;
Avatar avatar = entity.getAvatar();
this.setConstellation(targetPlayer, avatar, constLevel);
CommandHandler.sendTranslatedMessage(sender, "commands.setConst.success", avatar.getAvatarData().getName(), constLevel);
} catch (NumberFormatException ignored) {
CommandHandler.sendTranslatedMessage(sender, "commands.setConst.level_error");
}
}
private void setConstellation(Player player, Avatar avatar, int constLevel) {
int currentConstLevel = avatar.getCoreProudSkillLevel();
IntArrayList talentIds = new IntArrayList(avatar.getSkillDepot().getTalents());
Set<Integer> talentIdList = avatar.getTalentIdList();
talentIdList.clear();
avatar.setCoreProudSkillLevel(0);
for(int talent = 0; talent < constLevel; talent++) {
AvatarTalentData talentData = GameData.getAvatarTalentDataMap().get(talentIds.getInt(talent));
int mainCostItemId = talentData.getMainCostItemId();
player.getInventory().addItem(mainCostItemId);
Grasscutter.getGameServer().getInventorySystem().unlockAvatarConstellation(player, avatar.getGuid());
}
// force player to reload scene when necessary
if (constLevel < currentConstLevel) {
World world = player.getWorld();
Scene scene = player.getScene();
Position pos = player.getPosition();
world.transferPlayerToScene(player, 1, pos);
world.transferPlayerToScene(player, scene.getId(), pos);
scene.broadcastPacket(new PacketSceneEntityAppearNotify(player));
}
// ensure that all changes are visible to the player
avatar.recalcConstellations();
avatar.recalcStats(true);
avatar.save();
}
}
...@@ -6,14 +6,23 @@ import java.util.Map; ...@@ -6,14 +6,23 @@ import java.util.Map;
import emu.grasscutter.command.Command; import emu.grasscutter.command.Command;
import emu.grasscutter.command.CommandHandler; import emu.grasscutter.command.CommandHandler;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar; import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty; import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify; import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
@Command(label = "setStats", aliases = {"stats", "stat"}, usage = {"<stat> <value>"}, permission = "player.setstats", permissionTargeted = "player.setstats.others") @Command(
label = "setStats",
aliases = {"stats", "stat"},
usage = {
"[set] <stat> <value>",
"(lock|freeze) <stat> [<value>]", // Can lock to current value
"(unlock|unfreeze) <stat>"},
permission = "player.setstats",
permissionTargeted = "player.setstats.others")
public final class SetStatsCommand implements CommandHandler { public final class SetStatsCommand implements CommandHandler {
static class Stat { private static class Stat {
String name; String name;
FightProperty prop; FightProperty prop;
...@@ -27,9 +36,21 @@ public final class SetStatsCommand implements CommandHandler { ...@@ -27,9 +36,21 @@ public final class SetStatsCommand implements CommandHandler {
this.prop = prop; this.prop = prop;
} }
} }
Map<String, Stat> stats; private static enum Action {
ACTION_SET("commands.generic.set_to", "commands.generic.set_for_to"),
ACTION_LOCK("commands.setStats.locked_to", "commands.setStats.locked_for_to"),
ACTION_UNLOCK("commands.setStats.unlocked", "commands.setStats.unlocked_for");
public final String messageKeySelf;
public final String messageKeyOther;
private Action(String messageKeySelf, String messageKeyOther) {
this.messageKeySelf = messageKeySelf;
this.messageKeyOther = messageKeyOther;
}
}
private Map<String, Stat> stats;
public SetStatsCommand() { public SetStatsCommand() {
this.stats = new HashMap<>(); this.stats = new HashMap<>();
for (String key : FightProperty.getShortNames()) { for (String key : FightProperty.getShortNames()) {
...@@ -62,50 +83,97 @@ public final class SetStatsCommand implements CommandHandler { ...@@ -62,50 +83,97 @@ public final class SetStatsCommand implements CommandHandler {
this.stats.put("ephys", this.stats.get("phys%")); this.stats.put("ephys", this.stats.get("phys%"));
} }
public static float parsePercent(String input) throws NumberFormatException {
if (input.endsWith("%")) {
return Float.parseFloat(input.substring(0, input.length()-1))/100f;
} else {
return Float.parseFloat(input);
}
}
@Override @Override
public void execute(Player sender, Player targetPlayer, List<String> args) { public void execute(Player sender, Player targetPlayer, List<String> args) {
String statStr; String statStr = null;
String valueStr; String valueStr;
float value = 0f;
if (args.size() == 2) { if (args.size() < 2) {
statStr = args.get(0).toLowerCase();
valueStr = args.get(1);
} else {
sendUsageMessage(sender); sendUsageMessage(sender);
return; return;
} }
// Get the action and stat
String arg0 = args.remove(0).toLowerCase();
Action action = switch (arg0) {
default -> {statStr = arg0; yield Action.ACTION_SET;} // Implicit set command
case "set" -> Action.ACTION_SET; // Explicit set command
case "lock", "freeze" -> Action.ACTION_LOCK;
case "unlock", "unfreeze" -> Action.ACTION_UNLOCK;
};
if (statStr == null) {
statStr = args.remove(0).toLowerCase();
}
if (!stats.containsKey(statStr)) {
sendUsageMessage(sender); // Invalid stat or action
return;
}
Stat stat = stats.get(statStr);
EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity(); EntityAvatar entity = targetPlayer.getTeamManager().getCurrentAvatarEntity();
Avatar avatar = entity.getAvatar();
float value; // Get the value if the action requires it
try { try {
if (valueStr.endsWith("%")) { switch (action) {
value = Float.parseFloat(valueStr.substring(0, valueStr.length()-1))/100f; case ACTION_LOCK:
} else { if (args.isEmpty()) { // Lock to current value
value = Float.parseFloat(valueStr); value = avatar.getFightProperty(stat.prop);
break;
} // Else fall-through and lock to supplied value
case ACTION_SET:
value = parsePercent(args.remove(0));
break;
case ACTION_UNLOCK:
break;
} }
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.statValue"); CommandHandler.sendTranslatedMessage(sender, "commands.generic.invalid.statValue");
return; return;
} catch (IndexOutOfBoundsException ignored) {
sendUsageMessage(sender);
return;
} }
if (stats.containsKey(statStr)) { if (!args.isEmpty()) { // Leftover arguments!
Stat stat = stats.get(statStr);
entity.setFightProperty(stat.prop, value);
entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, stat.prop));
if (FightProperty.isPercentage(stat.prop)) {
valueStr = String.format("%.1f%%", value * 100f);
} else {
valueStr = String.format("%.0f", value);
}
if (targetPlayer == sender) {
CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_to", stat.name, valueStr);
} else {
String uidStr = targetPlayer.getAccount().getId();
CommandHandler.sendTranslatedMessage(sender, "commands.generic.set_for_to", stat.name, uidStr, valueStr);
}
} else {
sendUsageMessage(sender); sendUsageMessage(sender);
return;
}
switch (action) {
case ACTION_SET:
entity.setFightProperty(stat.prop, value);
entity.getWorld().broadcastPacket(new PacketEntityFightPropUpdateNotify(entity, stat.prop));
break;
case ACTION_LOCK:
avatar.getFightPropOverrides().put(stat.prop.getId(), value);
avatar.recalcStats();
break;
case ACTION_UNLOCK:
avatar.getFightPropOverrides().remove(stat.prop.getId());
avatar.recalcStats();
break;
}
// Report action
if (FightProperty.isPercentage(stat.prop)) {
valueStr = String.format("%.1f%%", value * 100f);
} else {
valueStr = String.format("%.0f", value);
}
if (targetPlayer == sender) {
CommandHandler.sendTranslatedMessage(sender, action.messageKeySelf, stat.name, valueStr);
} else {
String uidStr = targetPlayer.getAccount().getId();
CommandHandler.sendTranslatedMessage(sender, action.messageKeyOther, stat.name, uidStr, valueStr);
} }
return; return;
} }
......
...@@ -5,6 +5,7 @@ import emu.grasscutter.Grasscutter; ...@@ -5,6 +5,7 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerDebugMode; import emu.grasscutter.Grasscutter.ServerDebugMode;
import emu.grasscutter.Grasscutter.ServerRunMode; import emu.grasscutter.Grasscutter.ServerRunMode;
import java.nio.charset.StandardCharsets;
import java.util.Set; import java.util.Set;
import java.io.FileReader; import java.io.FileReader;
import java.lang.reflect.Field; import java.lang.reflect.Field;
...@@ -27,7 +28,7 @@ public class ConfigContainer { ...@@ -27,7 +28,7 @@ public class ConfigContainer {
public static void updateConfig() { public static void updateConfig() {
try { // Check if the server is using a legacy config. try { // Check if the server is using a legacy config.
JsonObject configObject = Grasscutter.getGsonFactory() JsonObject configObject = Grasscutter.getGsonFactory()
.fromJson(new FileReader(Grasscutter.configFile), JsonObject.class); .fromJson(new FileReader(Grasscutter.configFile, StandardCharsets.UTF_8), JsonObject.class);
if (!configObject.has("version")) { if (!configObject.has("version")) {
Grasscutter.getLogger().info("Updating legacy .."); Grasscutter.getLogger().info("Updating legacy ..");
Grasscutter.saveConfig(null); Grasscutter.saveConfig(null);
...@@ -121,7 +122,7 @@ public class ConfigContainer { ...@@ -121,7 +122,7 @@ public class ConfigContainer {
public static class HTTP { public static class HTTP {
public String bindAddress = "0.0.0.0"; public String bindAddress = "0.0.0.0";
public int bindPort = 443; public int bindPort = 443;
/* This is the address used in URLs. */ /* This is the address used in URLs. */
public String accessAddress = "127.0.0.1"; public String accessAddress = "127.0.0.1";
/* This is the port used in URLs. */ /* This is the port used in URLs. */
...@@ -145,7 +146,7 @@ public class ConfigContainer { ...@@ -145,7 +146,7 @@ public class ConfigContainer {
public int loadEntitiesForPlayerRange = 100; public int loadEntitiesForPlayerRange = 100;
public boolean enableScriptInBigWorld = false; public boolean enableScriptInBigWorld = false;
public boolean enableConsole = true; public boolean enableConsole = true;
/* Kcp internal work interval (milliseconds) */ /* Kcp internal work interval (milliseconds) */
public int kcpInterval = 20; public int kcpInterval = 20;
/* Controls whether packets should be logged in console or not */ /* Controls whether packets should be logged in console or not */
......
...@@ -8,6 +8,7 @@ import java.util.Map; ...@@ -8,6 +8,7 @@ import java.util.Map;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.binout.*; import emu.grasscutter.data.binout.*;
import emu.grasscutter.game.quest.QuestEncryptionKey;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import emu.grasscutter.data.excels.*; import emu.grasscutter.data.excels.*;
import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectLinkedOpenHashMap;
...@@ -25,12 +26,13 @@ public class GameData { ...@@ -25,12 +26,13 @@ public class GameData {
private static final Map<String, OpenConfigEntry> openConfigEntries = new HashMap<>(); private static final Map<String, OpenConfigEntry> openConfigEntries = new HashMap<>();
private static final Map<String, ScenePointEntry> scenePointEntries = new HashMap<>(); private static final Map<String, ScenePointEntry> scenePointEntries = new HashMap<>();
private static final Int2ObjectMap<MainQuestData> mainQuestData = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<MainQuestData> mainQuestData = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<QuestEncryptionKey> questsKeys = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<HomeworldDefaultSaveData> homeworldDefaultSaveData = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<HomeworldDefaultSaveData> homeworldDefaultSaveData = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<SceneNpcBornData> npcBornData = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<SceneNpcBornData> npcBornData = new Int2ObjectOpenHashMap<>();
// ExcelConfigs // ExcelConfigs
private static final Int2ObjectMap<PlayerLevelData> playerLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<PlayerLevelData> playerLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarData> avatarDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AvatarData> avatarDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarLevelData> avatarLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AvatarLevelData> avatarLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarSkillDepotData> avatarSkillDepotDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AvatarSkillDepotData> avatarSkillDepotDataMap = new Int2ObjectOpenHashMap<>();
...@@ -40,7 +42,7 @@ public class GameData { ...@@ -40,7 +42,7 @@ public class GameData {
private static final Int2ObjectMap<AvatarPromoteData> avatarPromoteDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AvatarPromoteData> avatarPromoteDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarTalentData> avatarTalentDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<AvatarTalentData> avatarTalentDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ProudSkillData> proudSkillDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<ProudSkillData> proudSkillDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ItemData> itemDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<ItemData> itemDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ReliquaryLevelData> reliquaryLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<ReliquaryLevelData> reliquaryLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<ReliquaryAffixData> reliquaryAffixDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<ReliquaryAffixData> reliquaryAffixDataMap = new Int2ObjectOpenHashMap<>();
...@@ -57,11 +59,11 @@ public class GameData { ...@@ -57,11 +59,11 @@ public class GameData {
private static final Int2ObjectMap<GadgetData> gadgetDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<GadgetData> gadgetDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<MonsterCurveData> monsterCurveDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<MonsterCurveData> monsterCurveDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<MonsterDescribeData> monsterDescribeDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<MonsterDescribeData> monsterDescribeDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<AvatarFlycloakData> avatarFlycloakDataMap = new Int2ObjectLinkedOpenHashMap<>(); private static final Int2ObjectMap<AvatarFlycloakData> avatarFlycloakDataMap = new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataMap = new Int2ObjectLinkedOpenHashMap<>(); private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataMap = new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataItemIdMap = new Int2ObjectLinkedOpenHashMap<>(); private static final Int2ObjectMap<AvatarCostumeData> avatarCostumeDataItemIdMap = new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<SceneData> sceneDataMap = new Int2ObjectLinkedOpenHashMap<>(); private static final Int2ObjectMap<SceneData> sceneDataMap = new Int2ObjectLinkedOpenHashMap<>();
private static final Int2ObjectMap<FetterData> fetterDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<FetterData> fetterDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<CodexQuestData> codexQuestDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<CodexQuestData> codexQuestDataMap = new Int2ObjectOpenHashMap<>();
...@@ -76,7 +78,7 @@ public class GameData { ...@@ -76,7 +78,7 @@ public class GameData {
private static final ArrayList<CodexReliquaryData> codexReliquaryArrayList = new ArrayList<>(); private static final ArrayList<CodexReliquaryData> codexReliquaryArrayList = new ArrayList<>();
private static final Int2ObjectMap<FetterCharacterCardData> fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<FetterCharacterCardData> fetterCharacterCardDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<RewardData> rewardDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<RewardData> rewardDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WorldAreaData> worldAreaDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<WorldAreaData> worldAreaDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<WorldLevelData> worldLevelDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<WorldLevelData> worldLevelDataMap = new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<DailyDungeonData> dailyDungeonDataMap = new Int2ObjectOpenHashMap<>(); private static final Int2ObjectMap<DailyDungeonData> dailyDungeonDataMap = new Int2ObjectOpenHashMap<>();
...@@ -108,6 +110,8 @@ public class GameData { ...@@ -108,6 +110,8 @@ public class GameData {
@Getter private static final Int2ObjectMap<MusicGameBasicData> musicGameBasicDataMap = new Int2ObjectOpenHashMap<>(); @Getter private static final Int2ObjectMap<MusicGameBasicData> musicGameBasicDataMap = new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<PersonalLineData> personalLineDataMap = new Int2ObjectOpenHashMap<>(); @Getter private static final Int2ObjectMap<PersonalLineData> personalLineDataMap = new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<ChapterData> chapterDataMap = new Int2ObjectOpenHashMap<>(); @Getter private static final Int2ObjectMap<ChapterData> chapterDataMap = new Int2ObjectOpenHashMap<>();
@Getter private static final Int2ObjectMap<TriggerExcelConfigData> triggerExcelConfigDataMap = new Int2ObjectOpenHashMap<>();
@Getter private static final Map<String,ScriptSceneData> scriptSceneDataMap = new HashMap<>();
@Getter private static final Int2ObjectMap<OpenStateData> openStateDataMap = new Int2ObjectOpenHashMap<>(); @Getter private static final Int2ObjectMap<OpenStateData> openStateDataMap = new Int2ObjectOpenHashMap<>();
...@@ -115,29 +119,33 @@ public class GameData { ...@@ -115,29 +119,33 @@ public class GameData {
private static Map<Integer, List<Integer>> fetters = new HashMap<>(); private static Map<Integer, List<Integer>> fetters = new HashMap<>();
private static Map<Integer, List<ShopGoodsData>> shopGoods = new HashMap<>(); private static Map<Integer, List<ShopGoodsData>> shopGoods = new HashMap<>();
private static final IntList scenePointIdList = new IntArrayList(); private static final IntList scenePointIdList = new IntArrayList();
@Getter private static final List<OpenStateData> openStateList = new ArrayList<>(); @Getter private static final List<OpenStateData> openStateList = new ArrayList<>();
public static Int2ObjectMap<?> getMapByResourceDef(Class<?> resourceDefinition) { public static Int2ObjectMap<?> getMapByResourceDef(Class<?> resourceDefinition) {
Int2ObjectMap<?> map = null; Int2ObjectMap<?> map = null;
try { try {
Field field = GameData.class.getDeclaredField(Utils.lowerCaseFirstChar(resourceDefinition.getSimpleName()) + "Map"); Field field = GameData.class.getDeclaredField(Utils.lowerCaseFirstChar(resourceDefinition.getSimpleName()) + "Map");
field.setAccessible(true); field.setAccessible(true);
map = (Int2ObjectMap<?>) field.get(null); map = (Int2ObjectMap<?>) field.get(null);
field.setAccessible(false); field.setAccessible(false);
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().error("Error fetching resource map for " + resourceDefinition.getSimpleName(), e); Grasscutter.getLogger().error("Error fetching resource map for " + resourceDefinition.getSimpleName(), e);
} }
return map; return map;
} }
public static Int2ObjectMap<String> getAbilityHashes() { public static Int2ObjectMap<String> getAbilityHashes() {
return abilityHashes; return abilityHashes;
} }
public static Map<String, AbilityEmbryoEntry> getAbilityEmbryoInfo() { public static Map<String, AbilityEmbryoEntry> getAbilityEmbryoInfo() {
return abilityEmbryos; return abilityEmbryos;
} }
...@@ -153,7 +161,7 @@ public class GameData { ...@@ -153,7 +161,7 @@ public class GameData {
public static Map<String, ScenePointEntry> getScenePointEntries() { public static Map<String, ScenePointEntry> getScenePointEntries() {
return scenePointEntries; return scenePointEntries;
} }
// TODO optimize // TODO optimize
public static ScenePointEntry getScenePointEntryById(int sceneId, int pointId) { public static ScenePointEntry getScenePointEntryById(int sceneId, int pointId) {
return getScenePointEntries().get(sceneId + "_" + pointId); return getScenePointEntries().get(sceneId + "_" + pointId);
...@@ -163,18 +171,22 @@ public class GameData { ...@@ -163,18 +171,22 @@ public class GameData {
return mainQuestData; return mainQuestData;
} }
public static Int2ObjectMap<QuestEncryptionKey> getMainQuestEncryptionMap() {
return questsKeys;
}
public static Int2ObjectMap<HomeworldDefaultSaveData> getHomeworldDefaultSaveData() { public static Int2ObjectMap<HomeworldDefaultSaveData> getHomeworldDefaultSaveData() {
return homeworldDefaultSaveData; return homeworldDefaultSaveData;
} }
public static Int2ObjectMap<SceneNpcBornData> getSceneNpcBornData() { public static Int2ObjectMap<SceneNpcBornData> getSceneNpcBornData() {
return npcBornData; return npcBornData;
} }
public static Int2ObjectMap<AvatarData> getAvatarDataMap() { public static Int2ObjectMap<AvatarData> getAvatarDataMap() {
return avatarDataMap; return avatarDataMap;
} }
public static Int2ObjectMap<ItemData> getItemDataMap() { public static Int2ObjectMap<ItemData> getItemDataMap() {
return itemDataMap; return itemDataMap;
} }
...@@ -182,7 +194,7 @@ public class GameData { ...@@ -182,7 +194,7 @@ public class GameData {
public static Int2ObjectMap<AvatarSkillDepotData> getAvatarSkillDepotDataMap() { public static Int2ObjectMap<AvatarSkillDepotData> getAvatarSkillDepotDataMap() {
return avatarSkillDepotDataMap; return avatarSkillDepotDataMap;
} }
public static Int2ObjectMap<AvatarSkillData> getAvatarSkillDataMap() { public static Int2ObjectMap<AvatarSkillData> getAvatarSkillDataMap() {
return avatarSkillDataMap; return avatarSkillDataMap;
} }
...@@ -206,11 +218,11 @@ public class GameData { ...@@ -206,11 +218,11 @@ public class GameData {
public static Int2ObjectMap<WeaponLevelData> getWeaponLevelDataMap() { public static Int2ObjectMap<WeaponLevelData> getWeaponLevelDataMap() {
return weaponLevelDataMap; return weaponLevelDataMap;
} }
public static Int2ObjectMap<ReliquaryAffixData> getReliquaryAffixDataMap() { public static Int2ObjectMap<ReliquaryAffixData> getReliquaryAffixDataMap() {
return reliquaryAffixDataMap; return reliquaryAffixDataMap;
} }
public static Int2ObjectMap<ReliquaryMainPropData> getReliquaryMainPropDataMap() { public static Int2ObjectMap<ReliquaryMainPropData> getReliquaryMainPropDataMap() {
return reliquaryMainPropDataMap; return reliquaryMainPropDataMap;
} }
...@@ -222,7 +234,7 @@ public class GameData { ...@@ -222,7 +234,7 @@ public class GameData {
public static Int2ObjectMap<WeaponCurveData> getWeaponCurveDataMap() { public static Int2ObjectMap<WeaponCurveData> getWeaponCurveDataMap() {
return weaponCurveDataMap; return weaponCurveDataMap;
} }
public static Int2ObjectMap<AvatarCurveData> getAvatarCurveDataMap() { public static Int2ObjectMap<AvatarCurveData> getAvatarCurveDataMap() {
return avatarCurveDataMap; return avatarCurveDataMap;
} }
...@@ -231,11 +243,11 @@ public class GameData { ...@@ -231,11 +243,11 @@ public class GameData {
ReliquaryLevelData levelData = reliquaryLevelDataMap.get((rankLevel << 8) + level); ReliquaryLevelData levelData = reliquaryLevelDataMap.get((rankLevel << 8) + level);
return levelData != null ? levelData.getExp() : 0; return levelData != null ? levelData.getExp() : 0;
} }
public static ReliquaryLevelData getRelicLevelData(int rankLevel, int level) { public static ReliquaryLevelData getRelicLevelData(int rankLevel, int level) {
return reliquaryLevelDataMap.get((rankLevel << 8) + level); return reliquaryLevelDataMap.get((rankLevel << 8) + level);
} }
public static WeaponPromoteData getWeaponPromoteData(int promoteId, int promoteLevel) { public static WeaponPromoteData getWeaponPromoteData(int promoteId, int promoteLevel) {
return weaponPromoteDataMap.get((promoteId << 8) + promoteLevel); return weaponPromoteDataMap.get((promoteId << 8) + promoteLevel);
} }
...@@ -251,7 +263,7 @@ public class GameData { ...@@ -251,7 +263,7 @@ public class GameData {
return 0; return 0;
} }
} }
public static AvatarPromoteData getAvatarPromoteData(int promoteId, int promoteLevel) { public static AvatarPromoteData getAvatarPromoteData(int promoteId, int promoteLevel) {
return avatarPromoteDataMap.get((promoteId << 8) + promoteLevel); return avatarPromoteDataMap.get((promoteId << 8) + promoteLevel);
} }
...@@ -265,7 +277,7 @@ public class GameData { ...@@ -265,7 +277,7 @@ public class GameData {
AvatarFetterLevelData levelData = avatarFetterLevelDataMap.get(level); AvatarFetterLevelData levelData = avatarFetterLevelDataMap.get(level);
return levelData != null ? levelData.getExp() : 0; return levelData != null ? levelData.getExp() : 0;
} }
public static Int2ObjectMap<ProudSkillData> getProudSkillDataMap() { public static Int2ObjectMap<ProudSkillData> getProudSkillDataMap() {
return proudSkillDataMap; return proudSkillDataMap;
} }
...@@ -312,7 +324,7 @@ public class GameData { ...@@ -312,7 +324,7 @@ public class GameData {
public static Int2ObjectMap<AvatarCostumeData> getAvatarCostumeDataMap() { public static Int2ObjectMap<AvatarCostumeData> getAvatarCostumeDataMap() {
return avatarCostumeDataMap; return avatarCostumeDataMap;
} }
public static Int2ObjectMap<AvatarCostumeData> getAvatarCostumeDataItemIdMap() { public static Int2ObjectMap<AvatarCostumeData> getAvatarCostumeDataItemIdMap() {
return avatarCostumeDataItemIdMap; return avatarCostumeDataItemIdMap;
} }
...@@ -353,7 +365,7 @@ public class GameData { ...@@ -353,7 +365,7 @@ public class GameData {
public static Int2ObjectMap<WorldAreaData> getWorldAreaDataMap() { public static Int2ObjectMap<WorldAreaData> getWorldAreaDataMap() {
return worldAreaDataMap; return worldAreaDataMap;
} }
public static Int2ObjectMap<WorldLevelData> getWorldLevelDataMap() { public static Int2ObjectMap<WorldLevelData> getWorldLevelDataMap() {
return worldLevelDataMap; return worldLevelDataMap;
} }
...@@ -361,7 +373,7 @@ public class GameData { ...@@ -361,7 +373,7 @@ public class GameData {
public static Int2ObjectMap<DungeonData> getDungeonDataMap() { public static Int2ObjectMap<DungeonData> getDungeonDataMap() {
return dungeonDataMap; return dungeonDataMap;
} }
public static Int2ObjectMap<DailyDungeonData> getDailyDungeonDataMap() { public static Int2ObjectMap<DailyDungeonData> getDailyDungeonDataMap() {
return dailyDungeonDataMap; return dailyDungeonDataMap;
} }
...@@ -397,11 +409,11 @@ public class GameData { ...@@ -397,11 +409,11 @@ public class GameData {
public static Int2ObjectMap<TowerFloorData> getTowerFloorDataMap(){ public static Int2ObjectMap<TowerFloorData> getTowerFloorDataMap(){
return towerFloorDataMap; return towerFloorDataMap;
} }
public static Int2ObjectMap<TowerLevelData> getTowerLevelDataMap(){ public static Int2ObjectMap<TowerLevelData> getTowerLevelDataMap(){
return towerLevelDataMap; return towerLevelDataMap;
} }
public static Int2ObjectMap<TowerScheduleData> getTowerScheduleDataMap(){ public static Int2ObjectMap<TowerScheduleData> getTowerScheduleDataMap(){
return towerScheduleDataMap; return towerScheduleDataMap;
} }
...@@ -413,19 +425,19 @@ public class GameData { ...@@ -413,19 +425,19 @@ public class GameData {
public static Int2ObjectMap<ForgeData> getForgeDataMap() { public static Int2ObjectMap<ForgeData> getForgeDataMap() {
return forgeDataMap; return forgeDataMap;
} }
public static Int2ObjectMap<HomeWorldLevelData> getHomeWorldLevelDataMap() { public static Int2ObjectMap<HomeWorldLevelData> getHomeWorldLevelDataMap() {
return homeWorldLevelDataMap; return homeWorldLevelDataMap;
} }
public static Int2ObjectMap<FurnitureMakeConfigData> getFurnitureMakeConfigDataMap() { public static Int2ObjectMap<FurnitureMakeConfigData> getFurnitureMakeConfigDataMap() {
return furnitureMakeConfigDataMap; return furnitureMakeConfigDataMap;
} }
public static Int2ObjectMap<GatherData> getGatherDataMap() { public static Int2ObjectMap<GatherData> getGatherDataMap() {
return gatherDataMap; return gatherDataMap;
} }
public static Int2ObjectMap<InvestigationMonsterData> getInvestigationMonsterDataMap() { public static Int2ObjectMap<InvestigationMonsterData> getInvestigationMonsterDataMap() {
return investigationMonsterDataMap; return investigationMonsterDataMap;
} }
......
package emu.grasscutter.data; package emu.grasscutter.data;
import java.io.*;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import emu.grasscutter.data.binout.*;
import emu.grasscutter.game.world.SpawnDataEntry;
import emu.grasscutter.scripts.SceneIndexManager;
import emu.grasscutter.utils.Utils;
import lombok.SneakyThrows;
import org.reflections.Reflections;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.binout.*;
import emu.grasscutter.data.binout.AbilityModifier.AbilityConfigData; import emu.grasscutter.data.binout.AbilityModifier.AbilityConfigData;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction; import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierActionType; import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierActionType;
import emu.grasscutter.data.common.PointData; import emu.grasscutter.data.common.PointData;
import emu.grasscutter.data.common.ScenePointConfig; import emu.grasscutter.data.common.ScenePointConfig;
import emu.grasscutter.game.world.SpawnDataEntry.*; import emu.grasscutter.game.quest.QuestEncryptionKey;
import emu.grasscutter.game.world.SpawnDataEntry;
import emu.grasscutter.game.world.SpawnDataEntry.GridBlockId;
import emu.grasscutter.game.world.SpawnDataEntry.SpawnGroupEntry;
import emu.grasscutter.scripts.SceneIndexManager;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import lombok.SneakyThrows;
import org.reflections.Reflections;
import static emu.grasscutter.config.Configuration.*; import java.io.*;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static emu.grasscutter.config.Configuration.RESOURCE;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
public class ResourceLoader { public class ResourceLoader {
...@@ -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"));
} }
...@@ -417,13 +418,49 @@ public class ResourceLoader { ...@@ -417,13 +418,49 @@ public class ResourceLoader {
GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest); GameData.getMainQuestDataMap().put(mainQuest.getId(), mainQuest);
} }
try (Reader reader = new FileReader(new File(RESOURCE("QuestEncryptionKeys.json")))) {
List<QuestEncryptionKey> keys = Grasscutter.getGsonFactory().fromJson(
reader,
TypeToken.getParameterized(List.class, QuestEncryptionKey.class).getType());
Int2ObjectMap<QuestEncryptionKey> questEncryptionMap = GameData.getMainQuestEncryptionMap();
keys.forEach(key -> questEncryptionMap.put(key.getMainQuestId(), key));
Grasscutter.getLogger().debug("Loaded {} quest keys.", questEncryptionMap.size());
} catch (FileNotFoundException ignored) {
Grasscutter.getLogger().error("Unable to load quest keys - ./resources/QuestEncryptionKeys.json not found.");
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load quest keys.", e);
}
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;
}
} }
package emu.grasscutter.data.binout;
import com.google.gson.annotations.SerializedName;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.utils.Position;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class ScriptSceneData {
Map<String,ScriptObject> scriptObjectList;
@Data
public static class ScriptObject {
//private SceneGroup groups;
@SerializedName("dummy_points")
private Map<String, List<Float>> dummyPoints;
}
}
...@@ -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;
}
} }
package emu.grasscutter.data.excels;
import emu.grasscutter.data.GameResource;
import emu.grasscutter.data.ResourceType;
import lombok.Getter;
@ResourceType(name = "TriggerExcelConfigData.json") @Getter
public class TriggerExcelConfigData extends GameResource {
@Getter private int id;
private int sceneId;
private int groupId;
private String triggerName;
}
...@@ -63,6 +63,7 @@ import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap; ...@@ -63,6 +63,7 @@ import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
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;
@Entity(value = "avatars", useDiscriminator = false) @Entity(value = "avatars", useDiscriminator = false)
public class Avatar { public class Avatar {
...@@ -85,6 +86,7 @@ public class Avatar { ...@@ -85,6 +86,7 @@ public class Avatar {
@Transient private final Int2ObjectMap<GameItem> equips; @Transient private final Int2ObjectMap<GameItem> equips;
@Transient private final Int2FloatOpenHashMap fightProp; @Transient private final Int2FloatOpenHashMap fightProp;
@Transient @Getter private final Int2FloatOpenHashMap fightPropOverrides;
@Transient private Set<String> extraAbilityEmbryos; @Transient private Set<String> extraAbilityEmbryos;
private List<Integer> fetters; private List<Integer> fetters;
...@@ -111,6 +113,7 @@ public class Avatar { ...@@ -111,6 +113,7 @@ public class Avatar {
public Avatar() { public Avatar() {
this.equips = new Int2ObjectOpenHashMap<>(); this.equips = new Int2ObjectOpenHashMap<>();
this.fightProp = new Int2FloatOpenHashMap(); this.fightProp = new Int2FloatOpenHashMap();
this.fightPropOverrides = new Int2FloatOpenHashMap();
this.extraAbilityEmbryos = new HashSet<>(); this.extraAbilityEmbryos = new HashSet<>();
this.proudSkillBonusMap = new HashMap<>(); this.proudSkillBonusMap = new HashMap<>();
this.fetters = new ArrayList<>(); // TODO Move to avatar this.fetters = new ArrayList<>(); // TODO Move to avatar
...@@ -728,6 +731,9 @@ public class Avatar { ...@@ -728,6 +731,9 @@ public class Avatar {
(getFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE) * (1f + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE) (getFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE) * (1f + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE_PERCENT))) + getFightProperty(FightProperty.FIGHT_PROP_DEFENSE)
); );
// Reapply all overrides
this.fightProp.putAll(this.fightPropOverrides);
// Set current hp // Set current hp
this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent); this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent);
......
...@@ -223,6 +223,10 @@ public class EntityGadget extends EntityBaseGadget { ...@@ -223,6 +223,10 @@ public class EntityGadget extends EntityBaseGadget {
.setIsEnableInteract(true) .setIsEnableInteract(true)
.setAuthorityPeerId(this.getScene().getWorld().getHostPeerId()); .setAuthorityPeerId(this.getScene().getWorld().getHostPeerId());
if(this.metaGadget != null) {
gadgetInfo.setDraftId(this.metaGadget.draft_id);
}
if (this.getContent() != null) { if (this.getContent() != null) {
this.getContent().onBuildProto(gadgetInfo); this.getContent().onBuildProto(gadgetInfo);
} }
......
...@@ -15,6 +15,7 @@ import java.util.concurrent.ConcurrentHashMap; ...@@ -15,6 +15,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class EntityRegion extends GameEntity{ public class EntityRegion extends GameEntity{
private final Position position; private final Position position;
private boolean hasNewEntities; private boolean hasNewEntities;
private boolean entityLeave;
private final Set<Integer> entities; // Ids of entities inside this region private final Set<Integer> entities; // Ids of entities inside this region
private final SceneRegion metaRegion; private final SceneRegion metaRegion;
...@@ -45,10 +46,17 @@ public class EntityRegion extends GameEntity{ ...@@ -45,10 +46,17 @@ public class EntityRegion extends GameEntity{
hasNewEntities = false; hasNewEntities = false;
} }
public void removeEntity(int entityId) {
this.getEntities().remove(entityId);
this.entityLeave = true;
}
public void removeEntity(GameEntity entity) { public void removeEntity(GameEntity entity) {
this.getEntities().remove(entity.getId()); this.getEntities().remove(entity.getId());
this.entityLeave = true;
} }
public boolean entityLeave() {return this.entityLeave;}
public void resetEntityLeave() {this.entityLeave = false;}
@Override @Override
public Int2FloatOpenHashMap getFightProperties() { public Int2FloatOpenHashMap getFightProperties() {
return null; return null;
......
...@@ -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,6 +1356,11 @@ public class Player { ...@@ -1327,6 +1356,11 @@ 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();
// Rewind active quests, and put the player to a rewind position it finds (if any) of an active quest
getQuestManager().onLogin();
// Packets // Packets
session.send(new PacketPlayerDataNotify(this)); // Player data session.send(new PacketPlayerDataNotify(this)); // Player data
session.send(new PacketStoreWeightLimitNotify()); session.send(new PacketStoreWeightLimitNotify());
......
...@@ -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,28 @@ public class GameMainQuest { ...@@ -131,24 +341,28 @@ 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());
for (GameQuest quest : this.getChildQuests().values()) {
ChildQuest childQuest = ChildQuest.newBuilder()
.setQuestId(quest.getQuestId())
.setState(quest.getState().getValue())
.build();
proto.addChildQuestList(childQuest); proto.setParentQuestState(getState().getValue())
} .setCutsceneEncryptionKey(QuestManager.getQuestKey(parentQuestId));
for (GameQuest quest : this.getChildQuests().values()) {
if (quest.getState() != QuestState.QUEST_STATE_UNSTARTED) {
ChildQuest childQuest = ChildQuest.newBuilder()
.setQuestId(quest.getSubQuestId())
.setState(quest.getState().getValue())
.build();
proto.addChildQuestList(childQuest);
}
}
for (int i : getQuestVars()) {
proto.addQuestVar(i);
}
if (getQuestVars() != null) {
for (int i : getQuestVars()) {
proto.addQuestVar(i);
}
}
return proto.build(); 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;
import lombok.AccessLevel;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
public class QuestEncryptionKey {
int mainQuestId;
long encryptionKey;
}
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 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;
...@@ -17,32 +16,134 @@ import emu.grasscutter.game.quest.enums.QuestTrigger; ...@@ -17,32 +16,134 @@ 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;
public QuestManager(Player player) { @Getter private final Player player;
@Getter private Map<Integer,Integer> questGlobalVariables;
@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 static long getQuestKey(int mainQuestId){
QuestEncryptionKey questEncryptionKey = GameData.getMainQuestEncryptionMap().get(mainQuestId);
return questEncryptionKey != null ? questEncryptionKey.getEncryptionKey() : 0L;
}
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 Int2ObjectMap<GameMainQuest> getQuests() { public void onLogin() {
return quests;
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));
}
}
} }
public GameMainQuest getMainQuestById(int mainQuestId) { private List<GameMainQuest> addMultMainQuests(Set<Integer> mainQuestIds) {
return getQuests().get(mainQuestId); 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;
} }
/*
Looking through mainQuests 72201-72208 and 72174, we can infer that a questGlobalVar's default value is 0
*/
public Integer getQuestGlobalVarValue(Integer variable) {
return this.questGlobalVariables.getOrDefault(variable,0);
}
public void setQuestGlobalVarValue(Integer variable, Integer value) {
Integer previousValue = this.questGlobalVariables.put(variable,value);
Grasscutter.getLogger().debug("Changed questGlobalVar {} value from {} to {}", variable, previousValue==null ? 0: previousValue, value);
}
public void incQuestGlobalVarValue(Integer variable, Integer inc) {
//
Integer previousValue = this.questGlobalVariables.getOrDefault(variable,0);
this.questGlobalVariables.put(variable,previousValue + inc);
Grasscutter.getLogger().debug("Incremented questGlobalVar {} value from {} to {}", variable, previousValue, previousValue + inc);
}
//In MainQuest 998, dec is passed as a positive integer
public void decQuestGlobalVarValue(Integer variable, Integer dec) {
//
Integer previousValue = this.questGlobalVariables.getOrDefault(variable,0);
this.questGlobalVariables.put(variable,previousValue - dec);
Grasscutter.getLogger().debug("Decremented questGlobalVar {} value from {} to {}", variable, previousValue, previousValue - dec);
}
public GameMainQuest getMainQuestById(int mainQuestId) {
return getMainQuests().get(mainQuestId);
}
public GameQuest getQuestById(int questId) { 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 +152,34 @@ public class QuestManager extends BasePlayerManager { ...@@ -51,34 +152,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 +203,16 @@ public class QuestManager extends BasePlayerManager { ...@@ -102,18 +203,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 +232,81 @@ public class QuestManager extends BasePlayerManager { ...@@ -133,55 +232,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 +320,16 @@ public class QuestManager extends BasePlayerManager { ...@@ -195,12 +320,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();
} }
} }
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