diff --git a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java index 0ff6402c7..77bf898cf 100644 --- a/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java +++ b/src/main/java/emu/grasscutter/game/quest/GameMainQuest.java @@ -132,14 +132,16 @@ public class GameMainQuest { // Add rewards MainQuestData mainQuestData = GameData.getMainQuestDataMap().get(this.getParentQuestId()); - for (int rewardId : mainQuestData.getRewardIdList()) { - RewardData rewardData = GameData.getRewardDataMap().get(rewardId); + if (mainQuestData != null && mainQuestData.getRewardIdList() != null) { + for (int rewardId : mainQuestData.getRewardIdList()) { + RewardData rewardData = GameData.getRewardDataMap().get(rewardId); - if (rewardData == null) { - continue; + if (rewardData == null) { + continue; + } + + getOwner().getInventory().addItemParamDatas(rewardData.getRewardItemList(), ActionReason.QuestReward); } - - getOwner().getInventory().addItemParamDatas(rewardData.getRewardItemList(), ActionReason.QuestReward); } // handoff main quest diff --git a/src/main/java/emu/grasscutter/game/quest/QuestManager.java b/src/main/java/emu/grasscutter/game/quest/QuestManager.java index 3d1df8471..ad4528a8d 100644 --- a/src/main/java/emu/grasscutter/game/quest/QuestManager.java +++ b/src/main/java/emu/grasscutter/game/quest/QuestManager.java @@ -272,6 +272,7 @@ public class QuestManager extends BasePlayerManager { case QUEST_CONTENT_INTERACT_GADGET: case QUEST_CONTENT_TRIGGER_FIRE: case QUEST_CONTENT_UNLOCK_TRANS_POINT: + case QUEST_CONTENT_SKILL: for (GameMainQuest mainQuest : checkMainQuests) { mainQuest.tryFinishSubQuests(condType, paramStr, params); } diff --git a/src/main/java/emu/grasscutter/game/quest/content/ContentSkill.java b/src/main/java/emu/grasscutter/game/quest/content/ContentSkill.java new file mode 100644 index 000000000..73401c3c0 --- /dev/null +++ b/src/main/java/emu/grasscutter/game/quest/content/ContentSkill.java @@ -0,0 +1,14 @@ +package emu.grasscutter.game.quest.content; + +import emu.grasscutter.data.excels.QuestData; +import emu.grasscutter.game.quest.GameQuest; +import emu.grasscutter.game.quest.QuestValue; +import emu.grasscutter.game.quest.enums.QuestTrigger; + +@QuestValue(QuestTrigger.QUEST_CONTENT_SKILL) +public class ContentSkill extends BaseContent { + @Override + public boolean execute(GameQuest quest, QuestData.QuestCondition condition, String paramStr, int... params) { + return (condition.getParam()[0] == params[0]) && (condition.getParam()[1] == params[1]); + } +} diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 02ebfbec5..9947a31f1 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -104,16 +104,22 @@ public class SceneScriptManager { currentTriggers.put(eventId, new HashSet<>()); } public void refreshGroup(SceneGroup group, int suiteIndex) { + if (group == null) { + return; + } + var suite = group.getSuiteByIndex(suiteIndex); if (suite == null) { return; } + if (suite.sceneTriggers.size() > 0) { for (var trigger : suite.sceneTriggers) { resetTriggers(trigger.event); this.currentTriggers.get(trigger.event).add(trigger); } } + spawnMonstersInGroup(group, suite); spawnGadgetsInGroup(group, suite); } diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index 946ec04e4..1dce83452 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -1,11 +1,13 @@ package emu.grasscutter.scripts; +import emu.grasscutter.database.DatabaseHelper; import emu.grasscutter.game.dungeons.challenge.DungeonChallenge; import emu.grasscutter.game.entity.EntityGadget; import emu.grasscutter.game.entity.EntityMonster; import emu.grasscutter.game.entity.GameEntity; import emu.grasscutter.game.entity.gadget.GadgetWorktop; import emu.grasscutter.game.dungeons.challenge.factory.ChallengeFactory; +import emu.grasscutter.game.player.Player; import emu.grasscutter.game.props.EntityType; import emu.grasscutter.game.quest.enums.QuestState; import emu.grasscutter.game.quest.enums.QuestTrigger; @@ -89,14 +91,14 @@ public class ScriptLib { .filter(e -> e instanceof EntityGadget) .map(e -> (EntityGadget)e) .forEach(e -> e.updateState(gadgetState)); - + return 0; } - + public int SetWorktopOptionsByGroupId(int groupId, int configId, int[] options) { logger.debug("[LUA] Call SetWorktopOptionsByGroupId with {},{},{}", groupId,configId,options); - + Optional entity = getSceneScriptManager().getScene().getEntities().values().stream() .filter(e -> e.getConfigId() == configId && e.getGroupId() == groupId).findFirst(); @@ -108,10 +110,10 @@ public class ScriptLib { if (!(gadget.getContent() instanceof GadgetWorktop worktop)) { return 1; } - + worktop.addWorktopOptions(options); getSceneScriptManager().getScene().broadcastPacket(new PacketWorktopOptionNotify(gadget)); - + return 0; } @@ -133,13 +135,13 @@ public class ScriptLib { if (!(gadget.getContent() instanceof GadgetWorktop worktop)) { return 1; } - + worktop.removeWorktopOption(option); getSceneScriptManager().getScene().broadcastPacket(new PacketWorktopOptionNotify(gadget)); - + return 0; } - + // Some fields are guessed public int AutoMonsterTide(int challengeIndex, int groupId, Integer[] ordersConfigId, int tideCount, int sceneLimit, int param6) { logger.debug("[LUA] Call AutoMonsterTide with {},{},{},{},{},{}", @@ -152,15 +154,15 @@ public class ScriptLib { } this.getSceneScriptManager().startMonsterTideInGroup(group, ordersConfigId, tideCount, sceneLimit); - + return 0; } - + public int AddExtraGroupSuite(int groupId, int suite) { logger.debug("[LUA] Call AddExtraGroupSuite with {},{}", groupId,suite); SceneGroup group = getSceneScriptManager().getGroupById(groupId); - + if (group == null || group.monsters == null) { return 1; } @@ -263,7 +265,7 @@ public class ScriptLib { challenge.start(); return 0; } - + public int GetGroupMonsterCountByGroupId(int groupId) { logger.debug("[LUA] Call GetGroupMonsterCountByGroupId with {}", groupId); @@ -271,20 +273,20 @@ public class ScriptLib { .filter(e -> e instanceof EntityMonster && e.getGroupId() == groupId) .count(); } - + public int GetGroupVariableValue(String var) { logger.debug("[LUA] Call GetGroupVariableValue with {}", var); return getSceneScriptManager().getVariables().getOrDefault(var, 0); } - + public int SetGroupVariableValue(String var, int value) { logger.debug("[LUA] Call SetGroupVariableValue with {},{}", var, value); getSceneScriptManager().getVariables().put(var, value); return 0; } - + public LuaValue ChangeGroupVariableValue(String var, int value) { logger.debug("[LUA] Call ChangeGroupVariableValue with {},{}", var, value); @@ -302,15 +304,15 @@ public class ScriptLib { // Kill and Respawn? int groupId = table.get("group_id").toint(); int suite = table.get("suite").toint(); - + SceneGroup group = getSceneScriptManager().getGroupById(groupId); - + if (group == null || group.monsters == null) { return 1; } - + getSceneScriptManager().refreshGroup(group, suite); - + return 0; } @@ -430,14 +432,14 @@ public class ScriptLib { var configId = table.get("config_id").toint(); var group = getCurrentGroup(); - + if (group.isEmpty()) { return 1; } - + var gadget = group.get().gadgets.get(configId); var entity = getSceneScriptManager().createGadget(group.get().id, group.get().block_id, gadget); - + getSceneScriptManager().addEntity(entity); return 0; @@ -515,6 +517,12 @@ public class ScriptLib { public int GetEntityType(int entityId){ var entity = getSceneScriptManager().getScene().getEntityById(entityId); if(entity == null){ + // check players + Player player = DatabaseHelper.getPlayerByUid(entityId); + if (player != null) { + return EntityType.Avatar.getValue(); + } + return EntityType.None.getValue(); } diff --git a/src/main/java/emu/grasscutter/scripts/data/Explore.java b/src/main/java/emu/grasscutter/scripts/data/Explore.java new file mode 100644 index 000000000..bfa4022e8 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/data/Explore.java @@ -0,0 +1,11 @@ +package emu.grasscutter.scripts.data; + +import lombok.Setter; +import lombok.ToString; + +@ToString +@Setter +public class Explore { + public String name; + public int exp; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java index 78bf4326c..132cc6b7b 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneGadget.java @@ -13,6 +13,13 @@ public class SceneGadget extends SceneObject{ public int interact_id; public boolean isOneoff; public int draft_id; + public String drop_tag; + public boolean persistent; + public int mark_flag; + public int route_id; + public Explore explore; + public int trigger_count; + public boolean showcutscene; public void setIsOneoff(boolean isOneoff) { this.isOneoff = isOneoff; diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java b/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java index a9bc586c9..267f0e227 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneInitConfig.java @@ -8,5 +8,5 @@ import lombok.ToString; public class SceneInitConfig { public int suite; public int end_suite; - public int rand_suite; + public boolean rand_suite; } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java index 070a87fee..3856883c2 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneMonster.java @@ -9,4 +9,11 @@ public class SceneMonster extends SceneObject{ public int monster_id; public int pose_id; public int drop_id; -} \ No newline at end of file + public int special_name_id; + public String drop_tag; + public int climate_area_id; + public boolean disableWander; + public int title_id; + public int[] affix; + public int mark_flag; +} diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java b/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java index 2d7a2879c..595957322 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneRegion.java @@ -4,6 +4,8 @@ import emu.grasscutter.scripts.constants.ScriptRegionShape; import emu.grasscutter.utils.Position; import lombok.Setter; +import java.util.List; + @Setter public class SceneRegion { @@ -14,6 +16,9 @@ public class SceneRegion { public Position size; // for SPHERE public int radius; + public int area_id; + public float height; + public List point_array; public transient SceneGroup group; public boolean contains(Position position) { diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java index 541d60036..d1309ee83 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneSuite.java @@ -9,12 +9,13 @@ import lombok.ToString; @ToString @Setter public class SceneSuite { - // make it refer the default empty list to avoid NPE caused by some group + // make it refer the default empty list to avoid NPE caused by some group public List monsters = List.of(); public List gadgets = List.of(); public List triggers = List.of(); public List regions = List.of(); public int rand_weight; + public int[] npcs; public transient List sceneMonsters = List.of(); public transient List sceneGadgets = List.of(); @@ -22,7 +23,7 @@ public class SceneSuite { public transient List sceneRegions = List.of(); public void init(SceneGroup sceneGroup) { - if(sceneGroup.monsters != null){ + if(sceneGroup.monsters != null && this.monsters != null){ this.sceneMonsters = new ArrayList<>( this.monsters.stream() .filter(sceneGroup.monsters::containsKey) @@ -31,7 +32,7 @@ public class SceneSuite { ); } - if(sceneGroup.gadgets != null){ + if(sceneGroup.gadgets != null && this.gadgets != null){ this.sceneGadgets = new ArrayList<>( this.gadgets.stream() .filter(sceneGroup.gadgets::containsKey) @@ -40,7 +41,7 @@ public class SceneSuite { ); } - if(sceneGroup.triggers != null) { + if(sceneGroup.triggers != null && this.triggers != null) { this.sceneTriggers = new ArrayList<>( this.triggers.stream() .filter(sceneGroup.triggers::containsKey) @@ -48,7 +49,7 @@ public class SceneSuite { .toList() ); } - if(sceneGroup.regions != null) { + if(sceneGroup.regions != null && this.regions != null) { this.sceneRegions = new ArrayList<>( this.regions.stream() .filter(sceneGroup.regions::containsKey) diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java index 34e4df75e..88e2e0891 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneTrigger.java @@ -10,6 +10,9 @@ public class SceneTrigger { public String source; public String condition; public String action; + public boolean forbid_guest; + public int trigger_count; + public String tlog_tag; public transient SceneGroup currentGroup; @Override @@ -34,6 +37,8 @@ public class SceneTrigger { ", source='" + source + '\'' + ", condition='" + condition + '\'' + ", action='" + action + '\'' + + ", forbid_guest='" + forbid_guest + '\'' + + ", trigger_count='" + trigger_count + '\'' + '}'; } } diff --git a/src/main/java/emu/grasscutter/scripts/data/SceneVar.java b/src/main/java/emu/grasscutter/scripts/data/SceneVar.java index 41ebda1b1..a95f78304 100644 --- a/src/main/java/emu/grasscutter/scripts/data/SceneVar.java +++ b/src/main/java/emu/grasscutter/scripts/data/SceneVar.java @@ -9,4 +9,5 @@ public class SceneVar { public String name; public int value; public boolean no_refresh; + public int configId; } diff --git a/src/main/java/emu/grasscutter/scripts/serializer/LuaTableJacksonSerializer.java b/src/main/java/emu/grasscutter/scripts/serializer/LuaTableJacksonSerializer.java new file mode 100644 index 000000000..ea2f314f1 --- /dev/null +++ b/src/main/java/emu/grasscutter/scripts/serializer/LuaTableJacksonSerializer.java @@ -0,0 +1,183 @@ +package emu.grasscutter.scripts.serializer; + +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.type.CollectionType; +import com.fasterxml.jackson.databind.type.MapType; +import emu.grasscutter.Grasscutter; +import org.luaj.vm2.LuaTable; +import org.luaj.vm2.LuaValue; + +import java.io.IOException; +import java.util.*; + +public class LuaTableJacksonSerializer extends JsonSerializer implements Serializer { + + private static ObjectMapper objectMapper; + + public LuaTableJacksonSerializer() { + if (objectMapper == null) { + objectMapper = new ObjectMapper(); + objectMapper.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true); + // Some properties in Lua table but not in java field + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configOverride(List.class).setSetterInfo(JsonSetter.Value.forContentNulls(Nulls.AS_EMPTY)); + SimpleModule luaSerializeModule = new SimpleModule(); + luaSerializeModule.addSerializer(LuaTable.class, this); + objectMapper.registerModule(luaSerializeModule); + } + } + + @Override + public void serialize(LuaTable value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + if (value == null || value.isnil()) { + gen.writeNull(); + return; + } + + // Detect table type + boolean isArray = false; + LuaValue[] keys = value.keys(); + if (keys.length == 0) { + gen.writeNull(); + return; + } + + int count = 0; + for (int i = 0; i < keys.length; i++) { + if (!keys[i].isint() || (i + 1) != keys[i].toint()) { + break; + } else { + count++; + } + } + + if (count == keys.length) { + isArray = true; + } + + if (isArray) { + gen.writeStartArray(); + for (LuaValue key : keys) { + LuaValue luaValue = value.get(key); + if (luaValue.isnil()) { + gen.writeNull(); + } else if (luaValue.isboolean()) { + gen.writeBoolean(luaValue.toboolean()); + } else if (luaValue.isint()) { + gen.writeNumber(luaValue.toint()); + } else if (luaValue.islong()) { + gen.writeNumber(luaValue.tolong()); + } else if (luaValue.isnumber()) { + gen.writeNumber(luaValue.tofloat()); + } else if (luaValue.isstring()) { + gen.writeString(luaValue.tojstring()); + } else if (luaValue.istable()) { + serialize(luaValue.checktable(), gen, serializers); + } + } + gen.writeEndArray(); + } else { + gen.writeStartObject(); + for (LuaValue key : keys) { + String keyStr = key.toString(); + LuaValue luaValue = value.get(key); + if (luaValue.isnil()) { + gen.writeNullField(keyStr); + } else if (luaValue.isboolean()) { + gen.writeBooleanField(keyStr, luaValue.toboolean()); + } else if (luaValue.isint()) { + gen.writeNumberField(keyStr, luaValue.toint()); + } else if (luaValue.islong()) { + gen.writeNumberField(keyStr, luaValue.tolong()); + } else if (luaValue.isnumber()) { + gen.writeNumberField(keyStr, luaValue.tofloat()); + } else if (luaValue.isstring()) { + gen.writeStringField(keyStr, luaValue.tojstring()); + } else if (luaValue.istable()) { + gen.writeFieldName(keyStr); + serialize(luaValue.checktable(), gen, serializers); + } + } + gen.writeEndObject(); + } + + gen.flush(); + gen.close(); + } + + @Override + public List toList(Class type, Object obj) { + List list = new ArrayList<>(); + if (!(obj instanceof LuaTable luaTable) || luaTable.isnil()) { + return list; + } + + CollectionType collectionType = objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, type); + JsonNode jsonNode = objectMapper.valueToTree(luaTable); + Grasscutter.getLogger().trace("[LuaTableToList] className={},data={}", type.getCanonicalName(), jsonNode.toString()); + if (jsonNode.isEmpty()) { + return list; + } + if (jsonNode.isArray()) { + try { + Object o = objectMapper.treeToValue(jsonNode, collectionType); + if (o != null) { + list = (ArrayList) o; + } + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } else if (jsonNode.isObject()) { + Iterator> fields = jsonNode.fields(); + List nodes = new ArrayList<>(); + while (fields.hasNext()) { + Map.Entry next = fields.next(); + nodes.add(next.getValue()); + } + list = objectMapper.convertValue(nodes, collectionType); + } + return list; + } + + @Override + public T toObject(Class type, Object obj) { + if (!(obj instanceof LuaTable luaTable) || luaTable.isnil()) { + return null; + } + + JsonNode jsonNode = objectMapper.valueToTree(luaTable); + Grasscutter.getLogger().trace("[LuaTableToObject] className={},data={}", type.getCanonicalName(), jsonNode.toString()); + try { + return objectMapper.treeToValue(jsonNode, type); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + return null; + } + + @Override + public Map toMap(Class type, Object obj) { + HashMap map = new HashMap<>(); + if (!(obj instanceof LuaTable luaTable) || luaTable.isnil()) { + return map; + } + + MapType mapStringType = objectMapper.getTypeFactory().constructMapType(HashMap.class, String.class, type); + JsonNode jsonNode = objectMapper.valueToTree(luaTable); + Grasscutter.getLogger().trace("[LuaTableToMap] className={},data={}", type.getCanonicalName(), jsonNode.toString()); + try { + Object o = objectMapper.treeToValue(jsonNode, mapStringType); + if (o != null) { + return (HashMap) o; + } + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + return map; + } +} diff --git a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java index 157c8fe22..41293f182 100644 --- a/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/recv/HandlerEvtDoSkillSuccNotify.java @@ -1,5 +1,6 @@ package emu.grasscutter.server.packet.recv; +import emu.grasscutter.game.quest.enums.QuestTrigger; import emu.grasscutter.net.packet.Opcodes; import emu.grasscutter.net.packet.PacketHandler; import emu.grasscutter.net.packet.PacketOpcodes; @@ -23,5 +24,6 @@ public class HandlerEvtDoSkillSuccNotify extends PacketHandler { // Handle skill notify in other managers. player.getStaminaManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); player.getEnergyManager().handleEvtDoSkillSuccNotify(session, skillId, casterId); + player.getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_SKILL, skillId, 0); } }