Create files from Grasscutter-Quests

these files are NOT directly compatible with Grasscutter, and require additional modifications to the codebase to work.
This commit is contained in:
KingRainbow44
2023-03-31 23:58:12 -04:00
parent 86aad96125
commit 262ee38ded
229 changed files with 6729 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
package emu.grasscutter.game.activity.condition;
import emu.grasscutter.data.excels.ActivityCondExcelConfigData;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* This annotation marks condition types for NewActivityCondExcelConfigData.json ({@link ActivityCondExcelConfigData}). To use it you should mark
* class that extends ActivityConditionBaseHandler, and it will be automatically picked during activity manager initiation.
*
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityCondition {
ActivityConditions value();
}

View File

@@ -0,0 +1,21 @@
package emu.grasscutter.game.activity.condition;
import emu.grasscutter.data.excels.ActivityCondExcelConfigData;
import emu.grasscutter.game.activity.ActivityConfigItem;
import emu.grasscutter.game.activity.PlayerActivityData;
/**
* Base handler for all activity conditions that are listed in NewActivityCondExcelConfigData.json ({@link ActivityCondExcelConfigData})
*/
public abstract class ActivityConditionBaseHandler {
/**
* Execute activity condition handler and return result of it's calculation
*
* @param activityData {@link PlayerActivityData} object containing info about activity
* @param activityConfig
* @param params params for handler
* @return result of condition calculation
*/
public abstract boolean execute(PlayerActivityData activityData, ActivityConfigItem activityConfig, int... params);
}

View File

@@ -0,0 +1,10 @@
package emu.grasscutter.game.activity.condition;
import java.util.List;
public interface ActivityConditionExecutor {
List<Integer> getMeetActivitiesConditions(List<Integer> condIds);
boolean meetsCondition(int activityCondId);
}

View File

@@ -0,0 +1,51 @@
package emu.grasscutter.game.activity.condition;
public enum ActivityConditions {
NEW_ACTIVITY_COND_PLAYER_LEVEL_GREAT_EQUAL,
NEW_ACTIVITY_COND_NOT_FINISH_TALK,
NEW_ACTIVITY_COND_SALESMAN_CAN_DELIVER,
NEW_ACTIVITY_COND_FINISH_PHOTO_POS_ID,
NEW_ACTIVITY_COND_HACHI_FINISH_STEALTH_STAGE_EQUAL,
NEW_ACTIVITY_COND_UNLOCKED_ALL_LISTED_SCENE_POINTS,
NEW_ACTIVITY_COND_DAYS_GREAT_EQUAL,
NEW_ACTIVITY_COND_FINISH_BARTENDER_LEVEL,
NEW_ACTIVITY_COND_FINISH_HACHI_STAGE,
NEW_ACTIVITY_COND_FINISH_ANY_INSTABLE_SPRAY_CHALLENGE_STAGE,
NEW_ACTIVITY_COND_HACHI_FINISH_BATTLE_STAGE_EQUAL,
NEW_ACTIVITY_COND_FINISH_CHANNELLER_SLAB_APPOINTED_STAGE_ALL_CAMP,
NEW_ACTIVITY_COND_FINISH_WATCHER,
NEW_ACTIVITY_COND_FINISH_REGION_SEARCH,
NEW_ACTIVITY_COND_FINISH_WATER_SPIRIT_PHASE,
NEW_ACTIVITY_COND_SEA_LAMP_POPULARIT,
NEW_ACTIVITY_COND_FINISH_DIG_ACTIVITY,
NEW_ACTIVITY_COND_FINISH_FIND_HILICHURL_LEVEL_EQUAL,
NEW_ACTIVITY_COND_GACHA_CAN_CREATE_ROBOT,
NEW_ACTIVITY_COND_FINISH_SALVAGE_STAGE,
NEW_ACTIVITY_COND_FINISH_MUSIC_GAME_ALL_LEVEL,
NEW_ACTIVITY_COND_DAYS_LESS,
NEW_ACTIVITY_COND_QUEST_FINISH,
NEW_ACTIVITY_COND_QUEST_GLOBAL_VAR_EQUAL,
NEW_ACTIVITY_COND_GROUP_BUNDLE_FINISHED,
NEW_ACTIVITY_COND_SEA_LAMP_PHASE,
NEW_ACTIVITY_COND_FINISH_CHANNELLER_SLAB_ANY_STAGE_ALL_CAMP,
NEW_ACTIVITY_COND_LUMINANCE_STONE_CHALLENGE_FINAL_GALLERY_COMPLETE,
NEW_ACTIVITY_COND_PLANT_FLOWER_CAN_DELIVER,
NEW_ACTIVITY_COND_LUMINANCE_STONE_CHALLENGE_STAGE_GREAT_EQUAL,
NEW_ACTIVITY_COND_FINISH_ANY_ARENA_CHALLENGE_LEVEL,
NEW_ACTIVITY_COND_FINISH_CUSTOM_DUNGEON_OFFICIAL,
NEW_ACTIVITY_COND_SCENE_MP_PLAY_ACTIVATED,
NEW_ACTIVITY_COND_FINISH_FIND_HILICHURL_LEVEL_LESS,
NEW_ACTIVITY_COND_TIME_GREATER,
NEW_ACTIVITY_COND_CREATE_NPC,
NEW_ACTIVITY_COND_TREASURE_SEELIE_FINISH_REGION,
NEW_ACTIVITY_COND_LUNA_RITE_ATMOSPHERE,
NEW_ACTIVITY_COND_OFFERING_LEVEL_GREAT_EQUAL,
NEW_ACTIVITY_COND_FINISH_CHANNELLER_SLAB_ANY_ONEOFF_DUNGEON,
NEW_ACTIVITY_COND_QUEST_FINISH_ALLOW_QUICK_OPEN,
NEW_ACTIVITY_COND_FINISH_POTION_ANY_LEVEL,
NEW_ACTIVITY_COND_MECHANICUS_OPEN,
NEW_ACTIVITY_COND_PLAYER_LEVEL_GREATER,
NEW_ACTIVITY_COND_SALESMAN_CAN_GET_REWARD,
NEW_ACTIVITY_COND_FINISH_REGION_SEARCH_LOGIC,
NEW_ACTIVITY_COND_FINISH_CHANNELLER_SLAB_ONEOFF_DUNGEON_IN_STAGE
}

View File

@@ -0,0 +1,60 @@
package emu.grasscutter.game.activity.condition;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.excels.ActivityCondExcelConfigData;
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
import org.reflections.Reflections;
import java.util.AbstractMap;
import java.util.Map;
import java.util.stream.Collectors;
/**
* Class that used for scanning classpath, picking up all activity conditions (for NewActivityCondExcelConfigData.json {@link ActivityCondExcelConfigData})
* and saving them to map. Check for more info {@link ActivityCondition}
*/
public class AllActivityConditionBuilder {
/**
* Build activity conditions handlers
*
* @return map containing all condition handlers for NewActivityCondExcelConfigData.json
*/
static public Map<ActivityConditions, ActivityConditionBaseHandler> buildActivityConditions() {
return new AllActivityConditionBuilder().initActivityConditions();
}
private Map<ActivityConditions, ActivityConditionBaseHandler> initActivityConditions() {
Reflections reflector = Grasscutter.reflector;
return reflector.getTypesAnnotatedWith(ActivityCondition.class).stream()
.map(this::newInstance)
.map(h -> new AbstractMap.SimpleEntry<>(extractActionType(h), h))
.collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
}
private ActivityConditions extractActionType(ActivityConditionBaseHandler e) {
ActivityCondition condition = e.getClass().getAnnotation(ActivityCondition.class);
if (condition == null) {
Grasscutter.getLogger().error("Failed to read command type for class {}", e.getClass().getName());
return null;
}
return condition.value();
}
private ActivityConditionBaseHandler newInstance(Class<?> clazz) {
try {
Object result = clazz.getDeclaredConstructor().newInstance();
if (result instanceof ActivityConditionBaseHandler) {
return (ActivityConditionBaseHandler) result;
}
Grasscutter.getLogger().error("Failed to initiate activity condition: {}, object have wrong type", clazz.getName());
} catch (Exception e) {
String message = String.format("Failed to initiate activity condition: %s, %s", clazz.getName(), e.getMessage());
Grasscutter.getLogger().error(message, e);
}
return null;
}
}

View File

@@ -0,0 +1,71 @@
package emu.grasscutter.game.activity.condition;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.excels.ActivityCondExcelConfigData;
import emu.grasscutter.game.activity.ActivityConfigItem;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.activity.condition.all.UnknownActivityConditionHandler;
import emu.grasscutter.game.quest.enums.LogicType;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.util.List;
import java.util.Map;
import java.util.function.BooleanSupplier;
import java.util.stream.Collectors;
public class BasicActivityConditionExecutor implements ActivityConditionExecutor {
private final Map<Integer, ActivityConfigItem> activityConfigItemMap;
private final Int2ObjectMap<ActivityCondExcelConfigData> activityConditions;
private final Int2ObjectMap<PlayerActivityData> playerActivityDataByActivityCondId;
private final Map<ActivityConditions, ActivityConditionBaseHandler> activityConditionsHandlers;
private static final UnknownActivityConditionHandler UNKNOWN_CONDITION_HANDLER = new UnknownActivityConditionHandler();
public BasicActivityConditionExecutor(Map<Integer, ActivityConfigItem> activityConfigItemMap,
Int2ObjectMap<ActivityCondExcelConfigData> activityConditions,
Int2ObjectMap<PlayerActivityData> playerActivityDataByActivityCondId,
Map<ActivityConditions, ActivityConditionBaseHandler> activityConditionsHandlers) {
this.activityConfigItemMap = activityConfigItemMap;
this.activityConditions = activityConditions;
this.playerActivityDataByActivityCondId = playerActivityDataByActivityCondId;
this.activityConditionsHandlers = activityConditionsHandlers;
}
@Override
public List<Integer> getMeetActivitiesConditions(List<Integer> condIds) {
return condIds.stream()
.filter(this::meetsCondition)
.collect(Collectors.toList());
}
@Override
public boolean meetsCondition(int activityCondId) {
ActivityCondExcelConfigData condData = activityConditions.get(activityCondId);
if (condData == null) {
Grasscutter.getLogger().error("Could not find condition for activity with id = {}", activityCondId);
return false;
}
LogicType condComb = condData.getCondComb();
if (condComb == null) {
condComb = LogicType.LOGIC_AND;
}
PlayerActivityData activity = playerActivityDataByActivityCondId.get(activityCondId);
if(activity==null){
return false;
}
ActivityConfigItem activityConfig = activityConfigItemMap.get(activity.getActivityId());
List<BooleanSupplier> predicates = condData.getCond()
.stream()
.map(c -> (BooleanSupplier) () ->
activityConditionsHandlers
.getOrDefault(c.getType(), UNKNOWN_CONDITION_HANDLER).execute(activity, activityConfig, c.paramArray()))
.collect(Collectors.toList());
return LogicType.calculate(condComb, predicates);
}
}

View File

@@ -0,0 +1,80 @@
package emu.grasscutter.game.activity.condition;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.ActivityCondExcelConfigData;
import emu.grasscutter.game.activity.PlayerActivityData;
import it.unimi.dsi.fastutil.ints.AbstractInt2ObjectMap.BasicEntry;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
import java.util.Map;
/**
* This class is used for building mapping for PlayerActivityData
*/
public class PlayerActivityDataMappingBuilder {
private final Map<Integer, PlayerActivityData> playerActivityDataMap;
private final Int2ObjectMap<ActivityCondExcelConfigData> activityCondMap;
/**
* Build mapping for PlayerActivityData.
*
* @return mapping for activity data. Key is condId from NewActivityCondExcelConfigData.json ({@link ActivityCondExcelConfigData}) resource,
* value is {@link PlayerActivityData} class for related activity.
*/
public static Int2ObjectMap<PlayerActivityData> buildPlayerActivityDataByActivityCondId(Map<Integer, PlayerActivityData> activities) {
return new PlayerActivityDataMappingBuilder(activities).buildMappings();
}
public PlayerActivityDataMappingBuilder(Map<Integer, PlayerActivityData> playerActivityDataMap) {
this.playerActivityDataMap = playerActivityDataMap;
activityCondMap = GameData.getActivityCondExcelConfigDataMap();
}
private Int2ObjectMap<PlayerActivityData> buildMappings() {
Int2ObjectMap<PlayerActivityData> result = new Int2ObjectRBTreeMap<>();
activityCondMap
.int2ObjectEntrySet()
.stream()
.map(entry -> new BasicEntry<>(entry.getIntKey(), getPlayerActivityDataByCondId(entry.getIntKey())))
.filter(entry -> entry.getValue() != null)
.forEach(entry -> result.put(entry.getIntKey(), entry.getValue()));
return result;
}
private PlayerActivityData getPlayerActivityDataByCondId(Integer key) {
return playerActivityDataMap.get(detectActivityDataIdByCondId(key));
}
/**
* Detect activity data id by cond id. Cond id comes from condId field from NewActivityCondExcelConfigData.json.
* See {@link ActivityCondExcelConfigData} for condId.
* <p>
* Generally, there are 3 cases:
* <ol>
* <li>Activity data id >= 5003. Then cond id will be activity data id plus 3 additional digits.
* For example: activity data id = 5087, cond id = 5087xxx (x - any digit)</li>
* <li>Activity data id = 5001. Then cond id will be activity data id plus 2 additional digits.
* For example: activity data id = 5001, cond id = 5001xx (x - any digit)</li>
* <li>Activity data id one of [1001]. Then cond id will be activity data id plus 2 additional digits.
* This also applied to activity data id = 1002. For example: activity data id = 1001, cond id = 1001x (x - any digit></li>
* </ol>
*
* @param key cond id for which activity data id should be defined
* @return activity data for given cond id. Returns -1 if activity was not found.
*/
private Integer detectActivityDataIdByCondId(Integer key) {
if (key / 10 == 1001 || key / 10 == 1002) {
return 1001;
} else if (key / 100 == 5001) {
return key / 100;
} else {
return key / 1000;
}
}
}

View File

@@ -0,0 +1,16 @@
package emu.grasscutter.game.activity.condition.all;
import emu.grasscutter.game.activity.ActivityConfigItem;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.activity.condition.ActivityCondition;
import emu.grasscutter.game.activity.condition.ActivityConditionBaseHandler;
import static emu.grasscutter.game.activity.condition.ActivityConditions.NEW_ACTIVITY_COND_DAYS_LESS;
@ActivityCondition(NEW_ACTIVITY_COND_DAYS_LESS)
public class DayLess extends ActivityConditionBaseHandler {
@Override
public boolean execute(PlayerActivityData activityData, ActivityConfigItem activityConfig, int... params) {
return true; //TODO implement this and add possibility to always return true
}
}

View File

@@ -0,0 +1,21 @@
package emu.grasscutter.game.activity.condition.all;
import emu.grasscutter.game.activity.ActivityConfigItem;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.activity.condition.ActivityCondition;
import emu.grasscutter.game.activity.condition.ActivityConditionBaseHandler;
import java.util.Date;
import static emu.grasscutter.game.activity.condition.ActivityConditions.NEW_ACTIVITY_COND_DAYS_GREAT_EQUAL;
@ActivityCondition(NEW_ACTIVITY_COND_DAYS_GREAT_EQUAL)
public class DaysGreatEqual extends ActivityConditionBaseHandler {
@Override
public boolean execute(PlayerActivityData activityData, ActivityConfigItem activityConfig, int... params) {
Date activityBeginTime = activityConfig.getBeginTime();
long timeDiff = System.currentTimeMillis() - activityBeginTime.getTime();
int days = (int) (timeDiff / (1000 * 60 * 60 * 24L));
return days + 1 >= params[0];
}
}

View File

@@ -0,0 +1,24 @@
package emu.grasscutter.game.activity.condition.all;
import emu.grasscutter.game.activity.ActivityConfigItem;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.activity.condition.ActivityCondition;
import emu.grasscutter.game.activity.condition.ActivityConditionBaseHandler;
import emu.grasscutter.game.activity.condition.ActivityConditions;
import lombok.val;
@ActivityCondition(ActivityConditions.NEW_ACTIVITY_COND_FINISH_WATCHER)
public class FinishWatcher extends ActivityConditionBaseHandler {
@Override
public boolean execute(PlayerActivityData activityData, ActivityConfigItem activityConfig, int... params) {
val watcherMap = activityData.getWatcherInfoMap();
for (int param : params) {
val watcher = watcherMap.get(param);
if(watcher == null || !watcher.isFinished()){
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,22 @@
package emu.grasscutter.game.activity.condition.all;
import emu.grasscutter.game.activity.ActivityConfigItem;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.activity.condition.ActivityCondition;
import emu.grasscutter.game.activity.condition.ActivityConditionBaseHandler;
import static emu.grasscutter.game.activity.condition.ActivityConditions.NEW_ACTIVITY_COND_NOT_FINISH_TALK;
@ActivityCondition(NEW_ACTIVITY_COND_NOT_FINISH_TALK)
public class NotFinishTalk extends ActivityConditionBaseHandler {
@Override
public boolean execute(PlayerActivityData activityData, ActivityConfigItem activityConfig, int... params) {
return activityData
.getPlayer()
.getQuestManager()
.getMainQuests()
.int2ObjectEntrySet()
.stream()
.noneMatch(q -> q.getValue().getTalks().get(params[0]) != null); //FIXME taken from ContentCompleteTalk
}
}

View File

@@ -0,0 +1,17 @@
package emu.grasscutter.game.activity.condition.all;
import emu.grasscutter.game.activity.ActivityConfigItem;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.activity.condition.ActivityCondition;
import emu.grasscutter.game.activity.condition.ActivityConditionBaseHandler;
import static emu.grasscutter.game.activity.condition.ActivityConditions.NEW_ACTIVITY_COND_PLAYER_LEVEL_GREAT_EQUAL;
@ActivityCondition(NEW_ACTIVITY_COND_PLAYER_LEVEL_GREAT_EQUAL)
public class PlayerLevelGreatEqualActivityActivityCondition extends ActivityConditionBaseHandler {
@Override
public boolean execute(PlayerActivityData activityData, ActivityConfigItem activityConfig, int... params) {
return activityData.getPlayer().getLevel() >= params[0];
}
}

View File

@@ -0,0 +1,23 @@
package emu.grasscutter.game.activity.condition.all;
import emu.grasscutter.game.activity.ActivityConfigItem;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.activity.condition.ActivityCondition;
import emu.grasscutter.game.activity.condition.ActivityConditionBaseHandler;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.enums.QuestState;
import static emu.grasscutter.game.activity.condition.ActivityConditions.NEW_ACTIVITY_COND_QUEST_FINISH;
@ActivityCondition(NEW_ACTIVITY_COND_QUEST_FINISH)
public class QuestFinished extends ActivityConditionBaseHandler {
@Override
public boolean execute(PlayerActivityData activityData, ActivityConfigItem activityConfig, int... params) {
GameQuest quest = activityData
.getPlayer()
.getQuestManager()
.getQuestById(params[0]);
return quest != null && quest.getState() == QuestState.QUEST_STATE_FINISHED;
}
}

View File

@@ -0,0 +1,19 @@
package emu.grasscutter.game.activity.condition.all;
import emu.grasscutter.game.activity.ActivityConfigItem;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.activity.condition.ActivityCondition;
import emu.grasscutter.game.activity.condition.ActivityConditionBaseHandler;
import static emu.grasscutter.game.activity.condition.ActivityConditions.NEW_ACTIVITY_COND_SALESMAN_CAN_DELIVER;
@ActivityCondition(NEW_ACTIVITY_COND_SALESMAN_CAN_DELIVER)
public class SalesmanCanDeliver extends ActivityConditionBaseHandler {
@Override
public boolean execute(PlayerActivityData activityData, ActivityConfigItem activityConfig, int... params) {
//TODO need to reverse engineer this logic.
//This condition appears only in one condition "condId": 5003001
//and this condition accept no params. I have no idea how to implement it
return false;
}
}

View File

@@ -0,0 +1,18 @@
package emu.grasscutter.game.activity.condition.all;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.activity.ActivityConfigItem;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.activity.condition.ActivityConditionBaseHandler;
/**
* This class is used when condition was not found
*/
public class UnknownActivityConditionHandler extends ActivityConditionBaseHandler {
@Override
public boolean execute(PlayerActivityData activityData, ActivityConfigItem activityConfig, int... params) {
Grasscutter.getLogger().error("Called unknown condition handler");
return false;
}
}

View File

@@ -0,0 +1,36 @@
package emu.grasscutter.game.activity.trialavatar;
import emu.grasscutter.game.activity.ActivityWatcher;
import emu.grasscutter.game.activity.ActivityWatcherType;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.props.WatcherTriggerType;
import lombok.val;
import java.util.stream.Stream;
@ActivityWatcherType(WatcherTriggerType.TRIGGER_FINISH_CHALLENGE)
public class TrialAvatarActivityChallengeTrigger extends ActivityWatcher {
@Override
protected boolean isMeet(String... param) {
if(param.length < 3) return false;
val handler = (TrialAvatarActivityHandler) getActivityHandler();
if(handler == null) return false;
val paramList = handler.getTriggerParamList();
if(paramList.isEmpty()) return false;
val paramCond = Stream.of(paramList.get(0).split(",")).toList();
return Stream.of(param).allMatch(x -> paramCond.contains(x));
}
@Override
public void trigger(PlayerActivityData playerActivityData, String... param) {
if (!isMeet(param)) return;
val handler = (TrialAvatarActivityHandler) getActivityHandler();
if(handler == null) return;
handler.setPassDungeon(playerActivityData);
}
}

View File

@@ -0,0 +1,144 @@
package emu.grasscutter.game.activity.trialavatar;
import com.esotericsoftware.reflectasm.ConstructorAccess;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.game.activity.ActivityWatcher;
import emu.grasscutter.game.activity.DefaultWatcher;
import emu.grasscutter.game.dungeons.DungeonTrialTeam;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.activity.ActivityHandler;
import emu.grasscutter.game.activity.GameActivity;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.net.proto.ActivityInfoOuterClass.ActivityInfo;
import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord.GrantReason;
import emu.grasscutter.server.packet.send.PacketActivityInfoNotify;
import emu.grasscutter.server.packet.send.PacketScenePlayerLocationNotify;
import emu.grasscutter.utils.JsonUtils;
import java.util.*;
import java.util.stream.*;
import lombok.*;
@GameActivity(ActivityType.NEW_ACTIVITY_TRIAL_AVATAR)
public class TrialAvatarActivityHandler extends ActivityHandler {
@Getter @Setter private int selectedTrialAvatarIndex;
@Override
public void onInitPlayerActivityData(PlayerActivityData playerActivityData) {
TrialAvatarPlayerData trialAvatarPlayerData = TrialAvatarPlayerData.create(getActivityConfigItem().getScheduleId());
playerActivityData.setDetail(trialAvatarPlayerData);
}
@Override
public void onProtoBuild(PlayerActivityData playerActivityData, ActivityInfo.Builder activityInfo) {
TrialAvatarPlayerData trialAvatarPlayerData = getTrialAvatarPlayerData(playerActivityData);
activityInfo.setTrialAvatarInfo(trialAvatarPlayerData.toProto());
}
@Override
public void initWatchers(Map<WatcherTriggerType, ConstructorAccess<?>> activityWatcherTypeMap) {
var watcherType = activityWatcherTypeMap.get(WatcherTriggerType.TRIGGER_FINISH_CHALLENGE);
ActivityWatcher watcher;
if(watcherType != null){
watcher = (ActivityWatcher) watcherType.newInstance();
}else{
watcher = new DefaultWatcher();
}
watcher.setActivityHandler(this);
getWatchersMap().computeIfAbsent(WatcherTriggerType.TRIGGER_FINISH_CHALLENGE, k -> new ArrayList<>());
getWatchersMap().get(WatcherTriggerType.TRIGGER_FINISH_CHALLENGE).add(watcher);
}
public TrialAvatarPlayerData getTrialAvatarPlayerData(PlayerActivityData playerActivityData) {
if (playerActivityData.getDetail() == null || playerActivityData.getDetail().isBlank()) {
onInitPlayerActivityData(playerActivityData);
playerActivityData.save();
}
return JsonUtils.decode(playerActivityData.getDetail(), TrialAvatarPlayerData.class);
}
public int getTrialActivityDungeonId(int trialAvatarIndexId) {
val data = GameData.getTrialAvatarActivityDataByAvatarIndex(trialAvatarIndexId);
return data!=null ? data.getDungeonId() : -1;
}
public List<String> getTriggerParamList() {
val data = GameData.getTrialAvatarActivityDataByAvatarIndex(getSelectedTrialAvatarIndex());
return data!=null ? data.getTriggerConfig().getParamList() : Collections.emptyList();
}
public boolean enterTrialDungeon(Player player, int trialAvatarIndexId, int enterPointId) {
// TODO, not sure if this will cause problem in MP, since we are entering trial activity dungeon
player.sendPacket(new PacketScenePlayerLocationNotify(player.getScene())); // official does send this
if (!player.getServer().getDungeonSystem().enterDungeon(
player,
enterPointId,
getTrialActivityDungeonId(trialAvatarIndexId))) return false;
setSelectedTrialAvatarIndex(trialAvatarIndexId);
return true;
}
public List<Integer> getBattleAvatarsList() {
val activityData = GameData.getTrialAvatarActivityDataByAvatarIndex(getSelectedTrialAvatarIndex());
if (activityData == null || activityData.getBattleAvatarsList().isBlank()) return List.of();
return Stream.of(activityData.getBattleAvatarsList().split(",")).map(Integer::parseInt).toList();
}
public DungeonTrialTeam getTrialAvatarDungeonTeam(){
List<Integer> battleAvatarsList = getBattleAvatarsList();
if (battleAvatarsList.isEmpty()) return null;
return new DungeonTrialTeam(battleAvatarsList, GrantReason.GRANT_REASON_BY_TRIAL_AVATAR_ACTIVITY);
}
public void unsetTrialAvatarTeam(Player player) {
if (getSelectedTrialAvatarIndex() <= 0) return;
player.removeTrialAvatarForActivity();
setSelectedTrialAvatarIndex(0);
}
public boolean getReward(Player player, int trialAvatarIndexId) {
val playerActivityData = player.getActivityManager().getPlayerActivityDataByActivityType(ActivityType.NEW_ACTIVITY_TRIAL_AVATAR);
if(playerActivityData.isEmpty()){
return false;
}
TrialAvatarPlayerData trialAvatarPlayerData = getTrialAvatarPlayerData(playerActivityData.get());
TrialAvatarPlayerData.RewardInfoItem rewardInfo = trialAvatarPlayerData.getRewardInfo(trialAvatarIndexId);
if (rewardInfo == null) return false;
RewardData rewardParam = GameData.getRewardDataMap().get(rewardInfo.getRewardId());
if (rewardParam == null) return false;
player.getInventory().addItemParamDatas(rewardParam.getRewardItemList(), ActionReason.TrialAvatarActivityFirstPassReward);
rewardInfo.setReceivedReward(true);
playerActivityData.get().setDetail(trialAvatarPlayerData);
playerActivityData.get().save();
return true;
}
public void setPassDungeon(PlayerActivityData playerActivityData) {
TrialAvatarPlayerData trialAvatarPlayerData = getTrialAvatarPlayerData(playerActivityData);
TrialAvatarPlayerData.RewardInfoItem rewardInfo = trialAvatarPlayerData.getRewardInfo(getSelectedTrialAvatarIndex());
if (rewardInfo == null) return;
rewardInfo.setPassedDungeon(true);
playerActivityData.setDetail(trialAvatarPlayerData);
playerActivityData.save();
Player player = Grasscutter.getGameServer().getPlayerByUid(playerActivityData.getUid());
player.sendPacket(new PacketActivityInfoNotify(toProto(playerActivityData, player.getActivityManager().getConditionExecutor())));
}
}

View File

@@ -0,0 +1,90 @@
package emu.grasscutter.game.activity.trialavatar;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.BaseTrialActivityData;
import emu.grasscutter.net.proto.TrialAvatarActivityDetailInfoOuterClass.TrialAvatarActivityDetailInfo;
import emu.grasscutter.net.proto.TrialAvatarActivityRewardDetailInfoOuterClass.TrialAvatarActivityRewardDetailInfo;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import lombok.val;
import java.util.List;
import java.util.stream.*;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class TrialAvatarPlayerData {
List<RewardInfoItem> rewardInfoList;
private static BaseTrialActivityData getActivityData(int scheduleId){
// prefer custom data over official data
return GameData.getTrialAvatarActivityCustomData().isEmpty() ? GameData.getTrialAvatarActivityDataMap().get(scheduleId)
: GameData.getTrialAvatarActivityCustomData().get(scheduleId);
}
public static List<Integer> getAvatarIdList(int scheduleId) {
val activityData = getActivityData(scheduleId);
return activityData != null ? activityData.getAvatarIndexIdList() : List.of();
}
public static List<Integer> getRewardIdList(int scheduleId) {
val activityData = getActivityData(scheduleId);
return activityData != null ? activityData.getRewardIdList() : List.of();
}
public static TrialAvatarPlayerData create(int scheduleId) {
List<Integer> avatarIds = getAvatarIdList(scheduleId);
List<Integer> rewardIds = getRewardIdList(scheduleId);
return TrialAvatarPlayerData.of()
.rewardInfoList(IntStream.range(0, avatarIds.size())
.filter(i -> avatarIds.get(i) > 0 && rewardIds.get(i) > 0)
.mapToObj(i -> RewardInfoItem.create(
avatarIds.get(i),
rewardIds.get(i)))
.collect(Collectors.toList()))
.build();
}
public TrialAvatarActivityDetailInfo toProto() {
return TrialAvatarActivityDetailInfo.newBuilder()
.addAllRewardInfoList(getRewardInfoList().stream()
.map(RewardInfoItem::toProto).toList())
.build();
}
public RewardInfoItem getRewardInfo(int trialAvatarIndexId) {
return getRewardInfoList().stream().filter(x -> x.getTrialAvatarIndexId() == trialAvatarIndexId)
.findFirst().orElse(null);
}
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class RewardInfoItem {
int trialAvatarIndexId;
int rewardId;
boolean passedDungeon;
boolean receivedReward;
public static RewardInfoItem create(int trialAvatarIndexId, int rewardId) {
return RewardInfoItem.of()
.trialAvatarIndexId(trialAvatarIndexId)
.rewardId(rewardId)
.passedDungeon(false)
.receivedReward(false)
.build();
}
public TrialAvatarActivityRewardDetailInfo toProto() {
return TrialAvatarActivityRewardDetailInfo.newBuilder()
.setTrialAvatarIndexId(getTrialAvatarIndexId())
.setRewardId(getRewardId())
.setPassedDungeon(isPassedDungeon())
.setReceivedReward(isReceivedReward())
.build();
}
}
}

View File

@@ -0,0 +1,18 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult;
import lombok.Getter;
public class DungeonEndStats {
@Getter private int killedMonsters;
@Getter private int timeTaken;
@Getter private int openChestCount;
@Getter private BaseDungeonResult.DungeonEndReason dungeonResult;
public DungeonEndStats(int killedMonsters, int timeTaken, int openChestCount, BaseDungeonResult.DungeonEndReason dungeonResult){
this.killedMonsters = killedMonsters;
this.timeTaken = timeTaken;
this.dungeonResult = dungeonResult;
this.openChestCount = openChestCount;
}
}

View File

@@ -0,0 +1,315 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.data.excels.DungeonPassConfigData;
import emu.grasscutter.game.activity.trialavatar.TrialAvatarActivityHandler;
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.ActivityType;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.quest.enums.LogicType;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketDungeonWayPointNotify;
import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import lombok.Getter;
import lombok.NonNull;
import lombok.val;
import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* TODO handle time limits
* TODO handle respawn points
* TODO handle team wipes and respawns
* TODO check monster level and levelConfigMap
*/
public class DungeonManager {
@Getter private final Scene scene;
@Getter private final DungeonData dungeonData;
@Getter private final DungeonPassConfigData passConfigData;
@Getter private final int[] finishedConditions;
private final IntSet rewardedPlayers = new IntOpenHashSet();
private final Set<Integer> activeDungeonWayPoints = new HashSet<>();
private boolean ended = false;
private int newestWayPoint = 0;
@Getter private int startSceneTime = 0;
DungeonTrialTeam trialTeam = null;
public DungeonManager(@NonNull Scene scene, @NonNull DungeonData dungeonData) {
this.scene = scene;
this.dungeonData = dungeonData;
this.passConfigData = GameData.getDungeonPassConfigDataMap().get(dungeonData.getPassCond());
this.finishedConditions = new int[passConfigData.getConds().size()];
this.scene.setDungeonManager(this);
}
public void triggerEvent(DungeonPassConditionType conditionType, int... params) {
if (ended) {
return;
}
for (int i = 0; i < passConfigData.getConds().size(); i++) {
var cond = passConfigData.getConds().get(i);
if (conditionType == cond.getCondType()) {
if (getScene().getWorld().getServer().getDungeonSystem().triggerCondition(cond, params)) {
finishedConditions[i] = 1;
}
}
}
if (isFinishedSuccessfully()) {
finishDungeon();
}
}
public boolean isFinishedSuccessfully() {
return LogicType.calculate(passConfigData.getLogicType(), finishedConditions);
}
public int getLevelForMonster(int id) {
//TODO should use levelConfigMap? and how?
return dungeonData.getShowLevel();
}
public boolean activateRespawnPoint(int pointId) {
val respawnPoint = GameData.getScenePointEntryById(scene.getId(), pointId);
if (respawnPoint == null) {
Grasscutter.getLogger().warn("trying to activate unknown respawn point {}", pointId);
return false;
}
scene.broadcastPacket(new PacketDungeonWayPointNotify(activeDungeonWayPoints.add(pointId), activeDungeonWayPoints));
newestWayPoint = pointId;
Grasscutter.getLogger().debug("[unimplemented respawn] activated respawn point {}", pointId);
return true;
}
@Nullable
public Position getRespawnLocation() {
if (newestWayPoint == 0) { // validity is checked before setting it, so if != 0 its always valid
return null;
}
val pointData = GameData.getScenePointEntryById(scene.getId(), newestWayPoint).getPointData();
return pointData.getTranPos() != null ? pointData.getTranPos() : pointData.getPos();
}
public Position getRespawnRotation() {
if (newestWayPoint == 0) { // validity is checked before setting it, so if != 0 its always valid
return null;
}
val pointData = GameData.getScenePointEntryById(scene.getId(), newestWayPoint).getPointData();
return pointData.getRot() != null ? pointData.getRot() : null;
}
public boolean getStatueDrops(Player player, boolean useCondensed, int groupId) {
if (!isFinishedSuccessfully() || dungeonData.getRewardPreviewData() == null || dungeonData.getRewardPreviewData().getPreviewItems().length == 0) {
return false;
}
// Already rewarded
if (rewardedPlayers.contains(player.getUid())) {
return false;
}
if (!handleCost(player, useCondensed)) {
return false;
}
// Get and roll rewards.
List<GameItem> rewards = new ArrayList<>(this.rollRewards(useCondensed));
// Add rewards to player and send notification.
player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop);
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards));
rewardedPlayers.add(player.getUid());
scene.getScriptManager().callEvent(new ScriptArgs(groupId, EventType.EVENT_DUNGEON_REWARD_GET));
return true;
}
public boolean handleCost(Player player, boolean useCondensed) {
int resinCost = dungeonData.getStatueCostCount() != 0 ? dungeonData.getStatueCostCount() : 20;
if (resinCost == 0) {
return true;
}
if (useCondensed) {
// Check if condensed resin is usable here.
// For this, we use the following logic for now:
// The normal resin cost of the dungeon has to be 20.
if (resinCost != 20) {
return false;
}
// Spend the condensed resin and only proceed if the transaction succeeds.
return player.getResinManager().useCondensedResin(1);
} else if (dungeonData.getStatueCostID() == 106) {
// Spend the resin and only proceed if the transaction succeeds.
return player.getResinManager().useResin(resinCost);
}
return true;
}
private List<GameItem> rollRewards(boolean useCondensed) {
List<GameItem> rewards = new ArrayList<>();
int dungeonId = this.dungeonData.getId();
// If we have specific drop data for this dungeon, we use it.
if (GameData.getDungeonDropDataMap().containsKey(dungeonId)) {
List<DungeonDropEntry> dropEntries = GameData.getDungeonDropDataMap().get(dungeonId);
// Roll for each drop group.
for (var entry : dropEntries) {
// Determine the number of drops we get for this entry.
int start = entry.getCounts().get(0);
int end = entry.getCounts().get(entry.getCounts().size() - 1);
var candidateAmounts = IntStream.range(start, end + 1).boxed().collect(Collectors.toList());
int amount = Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities());
if (useCondensed) {
amount += Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities());
}
// Double rewards in multiplay mode, if specified.
if (entry.isMpDouble() && this.getScene().getPlayerCount() > 1) {
amount *= 2;
}
// Roll items for this group.
// Here, we have to handle stacking, or the client will not display results correctly.
// For now, we use the following logic: If the possible drop item are a list of multiple items,
// we roll them separately. If not, we stack them. This should work out in practice, at least
// for the currently existing set of dungeons.
if (entry.getItems().size() == 1) {
rewards.add(new GameItem(entry.getItems().get(0), amount));
} else {
for (int i = 0; i < amount; i++) {
// int itemIndex = ThreadLocalRandom.current().nextInt(0, entry.getItems().size());
// int itemId = entry.getItems().get(itemIndex);
int itemId = Utils.drawRandomListElement(entry.getItems(), entry.getItemProbabilities());
rewards.add(new GameItem(itemId, 1));
}
}
}
}
// Otherwise, we fall back to the preview data.
else {
Grasscutter.getLogger().info("No drop data found or dungeon {}, falling back to preview data ...", dungeonId);
for (ItemParamData param : dungeonData.getRewardPreviewData().getPreviewItems()) {
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
}
}
return rewards;
}
public void applyTrialTeam(Player player) {
if (getDungeonData() == null) return;
switch (getDungeonData().getType()) {
// case DUNGEON_PLOT is handled by quest execs
case DUNGEON_ACTIVITY -> {
switch (getDungeonData().getPlayType()) {
case DUNGEON_PLAY_TYPE_TRIAL_AVATAR -> {
val activityHandler = player.getActivityManager()
.getActivityHandlerAs(ActivityType.NEW_ACTIVITY_TRIAL_AVATAR, TrialAvatarActivityHandler.class);
activityHandler.ifPresent(trialAvatarActivityHandler ->
this.trialTeam = trialAvatarActivityHandler.getTrialAvatarDungeonTeam());
}
}
}
case DUNGEON_ELEMENT_CHALLENGE -> {} // TODO
}
if(this.trialTeam != null) {
player.addTrialAvatarsForActivity(trialTeam.trialAvatarIds);
}
}
public void unsetTrialTeam(Player player){
if(this.trialTeam==null){
return;
}
player.removeTrialAvatarForActivity();
this.trialTeam = null;
}
public void startDungeon() {
this.startSceneTime = scene.getSceneTimeSeconds();
scene.getPlayers().forEach(p -> {
p.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_ENTER_DUNGEON, dungeonData.getId());
applyTrialTeam(p);
});
}
public void finishDungeon() {
notifyEndDungeon(true);
endDungeon(BaseDungeonResult.DungeonEndReason.COMPLETED);
}
public void notifyEndDungeon(boolean successfully) {
scene.getPlayers().forEach(p -> {
// Quest trigger
p.getQuestManager().queueEvent(successfully ?
QuestContent.QUEST_CONTENT_FINISH_DUNGEON : QuestContent.QUEST_CONTENT_FAIL_DUNGEON,
dungeonData.getId());
// Battle pass trigger
if (dungeonData.getType().isCountsToBattlepass() && successfully) {
p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON);
}
});
scene.getScriptManager().callEvent(new ScriptArgs(0, EventType.EVENT_DUNGEON_SETTLE, successfully ? 1 : 0));
}
public void quitDungeon() {
notifyEndDungeon(false);
endDungeon(BaseDungeonResult.DungeonEndReason.QUIT);
}
public void failDungeon() {
notifyEndDungeon(false);
endDungeon(BaseDungeonResult.DungeonEndReason.FAILED);
}
public void endDungeon(BaseDungeonResult.DungeonEndReason endReason) {
if (scene.getDungeonSettleListeners() != null) {
scene.getDungeonSettleListeners().forEach(o -> o.onDungeonSettle(this, endReason));
}
ended = true;
}
public void restartDungeon() {
this.scene.setKilledMonsterCount(0);
this.rewardedPlayers.clear();
Arrays.fill(finishedConditions, 0);
this.ended = false;
this.activeDungeonWayPoints.clear();
}
public void cleanUpScene() {
this.scene.setDungeonManager(null);
this.scene.setKilledMonsterCount(0);
}
}

View File

@@ -0,0 +1,14 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
@Data
@AllArgsConstructor
public class DungeonTrialTeam {
List<Integer> trialAvatarIds;
TrialAvatarGrantRecord.GrantReason grantReason;
}

View File

@@ -0,0 +1,11 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface DungeonValue {
DungeonPassConditionType value();
}

View File

@@ -0,0 +1,19 @@
package emu.grasscutter.game.dungeons.challenge.enums;
public enum ChallengeCondType {
CHALLENGE_COND_NONE, //00
CHALLENGE_COND_IN_TIME, //01
CHALLENGE_COND_ALL_TIME, //02
CHALLENGE_COND_KILL_COUNT, //03
CHALLENGE_COND_SURVIVE, //04
CHALLENGE_COND_TIME_INC, //05
CHALLENGE_COND_KILL_FAST, //06
CHALLENGE_COND_DOWN_LESS, //07
CHALLENGE_COND_BEATEN_LESS , //08
CHALLENGE_COND_UNNATURAL_COUNT , //09
CHALLENGE_COND_FROZEN_LESS , //10
CHALLENGE_COND_KILL_MONSTER , //11
CHALLENGE_COND_TRIGGER , //12
CHALLENGE_COND_GUARD_HP , //13
CHALLENGE_COND_TIME_DEC , //14
}

View File

@@ -0,0 +1,9 @@
package emu.grasscutter.game.dungeons.challenge.enums;
public enum ChallengeEventMarkType {
CHALLENGE_EVENT_NONE,
FLIGHT_TIME,
FLIGHT_GATHER_POINT,
SUMMER_TIME_SPRINT_BOAT_TIME,
SUMMER_TIME_SPRINT_BOAT_GATHER_POINT,
}

View File

@@ -0,0 +1,6 @@
package emu.grasscutter.game.dungeons.challenge.enums;
public enum ChallengeRecordType {
CHALLENGE_RECORD_TYPE_NONE,
CHALLENGE_RECORD_TYPE_IN_TIME
}

View File

@@ -0,0 +1,27 @@
package emu.grasscutter.game.dungeons.challenge.enums;
public enum ChallengeType {
CHALLENGE_NONE, //00
CHALLENGE_KILL_COUNT, //01
CHALLENGE_KILL_COUNT_IN_TIME, //02
CHALLENGE_SURVIVE, //03
CHALLENGE_TIME_FLY, //04
CHALLENGE_KILL_COUNT_FAST, //05
CHALLENGE_KILL_COUNT_FROZEN_LESS, //06
CHALLENGE_KILL_MONSTER_IN_TIME, //07
CHALLENGE_TRIGGER_IN_TIME, //08
CHALLENGE_GUARD_HP, //09
CHALLENGE_KILL_COUNT_GUARD_HP, //10
CHALLENGE_TRIGGER_IN_TIME_FLY , //11
//unknown if position and time match from here on
CHALLENGE_TRIGGER2_AVOID_TRIGGER1,
CHALLENGE_FATHER_SUCC_IN_TIME,
CHALLENGE_MONSTER_DAMAGE_COUNT,
CHALLENGE_ELEMENT_REACTION_COUNT,
CHALLENGE_FREEZE_ENEMY_IN_TIME,
CHALLENGE_CRYSTAL_ELEMENT_REACTION_COUNT,
CHALLENGE_SHEILD_ABSORB_DAMAGE_COUNT,
CHALLENGE_SWIRL_ELEMENT_REACTION_COUNT,
CHALLENGE_DIE_LESS_IN_TIME,
CHALLENGE_TRIGGER_COUNT,
}

View File

@@ -0,0 +1,9 @@
package emu.grasscutter.game.dungeons.challenge.enums;
public enum FatherChallengeProperty {
DURATION,
CUR_SUCC,
CUR_FAIL,
SUM_SUCC,
SUM_FAIL
}

View File

@@ -0,0 +1,34 @@
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.GuardTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import lombok.val;
import java.util.List;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_KILL_COUNT_GUARD_HP;
public class KillAndGuardChallengeFactoryHandler implements ChallengeFactoryHandler{
@Override
public boolean isThisType(ChallengeType challengeType) {
// ActiveChallenge with 1,188,234101003,12,3030,0
return challengeType == CHALLENGE_KILL_COUNT_GUARD_HP;
}
@Override /*TODO check param4 == monstesToKill*/
public WorldChallenge build(int challengeIndex, int challengeId, int groupId, int monstersToKill, int gadgetCFGId, int unused, Scene scene, SceneGroup group) {
val realGroup = scene.getScriptManager().getGroupById(groupId);
return new WorldChallenge(
scene, realGroup,
challengeId, // Id
challengeIndex, // Index
List.of(monstersToKill, 0),
0, // Limit
monstersToKill, // Goal
List.of(new KillMonsterCountTrigger(), new GuardTrigger(gadgetCFGId)));
}
}

View File

@@ -0,0 +1,32 @@
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import lombok.val;
import java.util.List;
public class KillMonsterCountChallengeFactoryHandler implements ChallengeFactoryHandler{
@Override
public boolean isThisType(ChallengeType challengeType) {
// ActiveChallenge with 1, 1, 241033003, 15, 0, 0
return challengeType == ChallengeType.CHALLENGE_KILL_COUNT;
}
@Override
public WorldChallenge build(int challengeIndex, int challengeId, int groupId, int goal, int param5, int param6, Scene scene, SceneGroup group) {
val realGroup = scene.getScriptManager().getGroupById(groupId);
return new WorldChallenge(
scene, realGroup,
challengeId, // Id
challengeIndex, // Index
List.of(goal, groupId),
0, // Limit
goal, // Goal
List.of(new KillMonsterCountTrigger())
);
}
}

View File

@@ -0,0 +1,33 @@
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import lombok.val;
import java.util.List;
public class KillMonsterInTimeChallengeFactoryHandler implements ChallengeFactoryHandler{
@Override
public boolean isThisType(ChallengeType challengeType) {
// ActiveChallenge with 180, 72, 240, 133220161, 133220161, 0
return challengeType == ChallengeType.CHALLENGE_KILL_MONSTER_IN_TIME;
}
@Override
public WorldChallenge build(int challengeIndex, int challengeId, int timeLimit, int groupId, int targetCfgId, int param6, Scene scene, SceneGroup group) {
val realGroup = scene.getScriptManager().getGroupById(groupId);
return new WorldChallenge(
scene, realGroup,
challengeId, // Id
challengeIndex, // Index
List.of(timeLimit),
timeLimit, // Limit
0, // Goal
List.of(new KillMonsterTrigger(targetCfgId), new InTimeTrigger())
);
}
}

View File

@@ -0,0 +1,35 @@
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import lombok.val;
import java.util.List;
public class KillMonsterTimeChallengeFactoryHandler implements ChallengeFactoryHandler{
@Override
public boolean isThisType(ChallengeType challengeType) {
// ActiveChallenge with 180,180,45,133108061,1,0
// ActiveChallenge Fast with 1001, 5, 15, 240004005, 10, 0
return challengeType == ChallengeType.CHALLENGE_KILL_COUNT_IN_TIME ||
challengeType == ChallengeType.CHALLENGE_KILL_COUNT_FAST;
}
@Override
public WorldChallenge build(int challengeIndex, int challengeId, int timeLimit, int groupId, int targetCount, int param6, Scene scene, SceneGroup group) {
val realGroup = scene.getScriptManager().getGroupById(groupId);
return new WorldChallenge(
scene, realGroup,
challengeId, // Id
challengeIndex, // Index
List.of(targetCount, timeLimit),
timeLimit, // Limit
targetCount, // Goal
List.of(new KillMonsterCountTrigger(), new InTimeTrigger())
);
}
}

View File

@@ -0,0 +1,33 @@
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.ForTimeTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_SURVIVE;
public class SurviveChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override
public boolean isThisType(ChallengeType challengeType) {
// grp 201055005
// ActiveChallenge with 100, 56, 60, 0, 0, 0
return challengeType == CHALLENGE_SURVIVE;
}
@Override
public WorldChallenge build(int challengeIndex, int challengeId, int timeToSurvive, int unused4, int unused5, int unused6, Scene scene, SceneGroup group) {
return new WorldChallenge(
scene, group,
challengeId, // Id
challengeIndex, // Index
List.of(timeToSurvive),
timeToSurvive, // Limit
0, // Goal
List.of(new ForTimeTrigger())
);
}
}

View File

@@ -0,0 +1,36 @@
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.TriggerGroupTriggerTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_TRIGGER_IN_TIME;
public class TriggerInTimeChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override
public boolean isThisType(ChallengeType challengeType) {
// kill gadgets(explosive barrel) in time
// ActiveChallenge with 56,201,20,2,201,4
// open chest in time
// ActiveChallenge with 666,202,30,7,202,1
return challengeType == CHALLENGE_TRIGGER_IN_TIME;
}
@Override
public WorldChallenge build(int challengeIndex, int challengeId, int timeLimit, int param4, int triggerTag, int triggerCount, Scene scene, SceneGroup group) {
return new WorldChallenge(
scene, group,
challengeId, // Id
challengeIndex, // Index
List.of(timeLimit, triggerCount),
timeLimit, // Limit
triggerCount, // Goal
List.of(new InTimeTrigger(), new TriggerGroupTriggerTrigger(Integer.toString(triggerTag)))
);
}
}

View File

@@ -0,0 +1,13 @@
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
public class ForTimeTrigger extends ChallengeTrigger{
@Override
public void onCheckTimeout(WorldChallenge challenge) {
var current = challenge.getScene().getSceneTimeSeconds();
if(current - challenge.getStartedAt() > challenge.getTimeLimit()){
challenge.done();
}
}
}

View File

@@ -0,0 +1,23 @@
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
public class KillMonsterCountTrigger extends ChallengeTrigger{
@Override
public void onBegin(WorldChallenge challenge) {
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 1, challenge.getScore().get()));
}
@Override
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {
var newScore = challenge.increaseScore();
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 1, newScore));
if(newScore >= challenge.getGoal()){
challenge.done();
}
}
}

View File

@@ -0,0 +1,30 @@
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.scripts.data.SceneTrigger;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class TriggerGroupTriggerTrigger extends ChallengeTrigger{
String triggerTag;
@Override
public void onBegin(WorldChallenge challenge) {
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, challenge.getScore().get()));
}
@Override
public void onGroupTrigger(WorldChallenge challenge, SceneTrigger trigger) {
if(!triggerTag.equals(trigger.getTag())) {
return;
}
var newScore = challenge.increaseScore();
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, newScore));
if(newScore >= challenge.getGoal()){
challenge.done();
}
}
}

View File

@@ -0,0 +1,74 @@
package emu.grasscutter.game.dungeons.dungeon_results;
import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.game.dungeons.DungeonEndStats;
import emu.grasscutter.net.proto.DungeonSettleNotifyOuterClass.DungeonSettleNotify;
import emu.grasscutter.net.proto.ParamListOuterClass;
import emu.grasscutter.utils.Utils;
import lombok.Getter;
public class BaseDungeonResult {
@Getter DungeonData dungeonData;
@Getter
DungeonEndStats dungeonStats;
public BaseDungeonResult(DungeonData dungeonData, DungeonEndStats dungeonStats){
this.dungeonData = dungeonData;
this.dungeonStats = dungeonStats;
}
protected void onProto(DungeonSettleNotify.Builder builder){ }
public final DungeonSettleNotify.Builder getProto(){
var success = dungeonStats.getDungeonResult().isSuccess();
var builder = DungeonSettleNotify.newBuilder()
.setDungeonId(dungeonData.getId())
.setIsSuccess(success)
.setCloseTime(getCloseTime())
.setResult(success ? 1 : 0);
// TODO check
if(dungeonData.getSettleShows()!=null) {
for (int i = 0; i < dungeonData.getSettleShows().size(); i++) {
var settle = dungeonData.getSettleShows().get(i);
builder.putSettleShow(i + 1,switch (settle) {
case SETTLE_SHOW_TIME_COST -> ParamListOuterClass.ParamList.newBuilder()
.addParamList(settle.getId())
.addParamList(dungeonStats.getTimeTaken())
.build();
case SETTLE_SHOW_KILL_MONSTER_COUNT -> ParamListOuterClass.ParamList.newBuilder()
.addParamList(settle.getId())
.addParamList(dungeonStats.getKilledMonsters())
.build();
default -> ParamListOuterClass.ParamList.newBuilder()
.addParamList(settle.getId())
.build();
});
}
}
//TODO handle settle show
onProto(builder);
return builder;
}
public int getCloseTime(){
return Utils.getCurrentSeconds() + switch (dungeonStats.getDungeonResult()){
case COMPLETED -> dungeonData.getSettleCountdownTime();
case FAILED -> dungeonData.getFailSettleCountdownTime();
case QUIT -> dungeonData.getQuitSettleCountdownTime();
};
}
public enum DungeonEndReason{
COMPLETED,
FAILED,
QUIT;
public boolean isSuccess(){
return this == COMPLETED;
}
}
}

View File

@@ -0,0 +1,50 @@
package emu.grasscutter.game.dungeons.dungeon_results;
import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.game.dungeons.DungeonEndStats;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.tower.TowerManager;
import emu.grasscutter.net.proto.DungeonSettleExhibitionInfoOuterClass;
import emu.grasscutter.net.proto.DungeonSettleNotifyOuterClass;
import emu.grasscutter.net.proto.ItemParamOuterClass;
import emu.grasscutter.net.proto.TowerLevelEndNotifyOuterClass.TowerLevelEndNotify.ContinueStateType;
import emu.grasscutter.net.proto.TowerLevelEndNotifyOuterClass.TowerLevelEndNotify;
public class TowerResult extends BaseDungeonResult{
WorldChallenge challenge;
boolean canJump;
boolean hasNextLevel;
int nextFloorId;
public TowerResult(DungeonData dungeonData, DungeonEndStats dungeonStats, TowerManager towerManager, WorldChallenge challenge) {
super(dungeonData, dungeonStats);
this.challenge = challenge;
this.canJump = towerManager.hasNextFloor();
this.hasNextLevel = towerManager.hasNextLevel();
this.nextFloorId = hasNextLevel ? 0 : towerManager.getNextFloorId();
}
@Override
protected void onProto(DungeonSettleNotifyOuterClass.DungeonSettleNotify.Builder builder) {
var continueStatus = ContinueStateType.CONTINUE_STATE_TYPE_CAN_NOT_CONTINUE_VALUE;
if(challenge.isSuccess() && canJump){
continueStatus = hasNextLevel ? ContinueStateType.CONTINUE_STATE_TYPE_CAN_ENTER_NEXT_LEVEL_VALUE
: ContinueStateType.CONTINUE_STATE_TYPE_CAN_ENTER_NEXT_FLOOR_VALUE;
}
var towerLevelEndNotify = TowerLevelEndNotify.newBuilder()
.setIsSuccess(challenge.isSuccess())
.setContinueState(continueStatus)
.addFinishedStarCondList(1)
.addFinishedStarCondList(2)
.addFinishedStarCondList(3)
.addRewardItemList(ItemParamOuterClass.ItemParam.newBuilder()
.setItemId(201)
.setCount(1000)
.build())
;
if(nextFloorId > 0 && canJump){
towerLevelEndNotify.setNextFloorId(nextFloorId);
}
builder.setTowerLevelEndNotify(towerLevelEndNotify);
}
}

View File

@@ -0,0 +1,23 @@
package emu.grasscutter.game.dungeons.dungeon_results;
import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.game.dungeons.DungeonEndStats;
import emu.grasscutter.net.proto.DungeonSettleNotifyOuterClass;
import emu.grasscutter.net.proto.TrialAvatarFirstPassDungeonNotifyOuterClass.TrialAvatarFirstPassDungeonNotify;
public class TrialAvatarDungeonResult extends BaseDungeonResult {
int trialCharacterIndexId;
public TrialAvatarDungeonResult(DungeonData dungeonData, DungeonEndStats dungeonStats, int trialCharacterIndexId) {
super(dungeonData, dungeonStats);
this.trialCharacterIndexId = trialCharacterIndexId;
}
@Override
protected void onProto(DungeonSettleNotifyOuterClass.DungeonSettleNotify.Builder builder) {
if (dungeonStats.getDungeonResult() == DungeonEndReason.COMPLETED) { //TODO check if its the first pass(?)
builder.setTrialAvatarFirstPassDungeonNotify(TrialAvatarFirstPassDungeonNotify.newBuilder()
.setTrialAvatarIndexId(trialCharacterIndexId));
}
}
}

View File

@@ -0,0 +1,7 @@
package emu.grasscutter.game.dungeons.enums;
public enum DungeonEntrySatisfiedConditionType {
DUNGEON_ENTRY_CONDITION_NONE,
DUNGEON_ENTRY_CONDITION_LEVEL,
DUNGEON_ENTRY_CONDITION_QUEST
}

View File

@@ -0,0 +1,7 @@
package emu.grasscutter.game.dungeons.enums;
public enum DungeonInvolveType {
INVOLVE_NONE,
INVOLVE_ONLY_SINGLE,
INVOLVE_SINGLE_MULTIPLE
}

View File

@@ -0,0 +1,27 @@
package emu.grasscutter.game.dungeons.enums;
import emu.grasscutter.scripts.constants.IntValueEnum;
import lombok.Getter;
public enum DungeonPassConditionType implements IntValueEnum {
DUNGEON_COND_NONE(0),
DUNGEON_COND_KILL_MONSTER(3),
DUNGEON_COND_KILL_GROUP_MONSTER(5),
DUNGEON_COND_KILL_TYPE_MONSTER(7),
DUNGEON_COND_FINISH_QUEST(9),
DUNGEON_COND_KILL_MONSTER_COUNT(11), // TODO handle count
DUNGEON_COND_IN_TIME(13), // Missing triggers and tracking
DUNGEON_COND_FINISH_CHALLENGE(14),
DUNGEON_COND_END_MULTISTAGE_PLAY(15) // Missing
;
@Getter private final int id;
DungeonPassConditionType(int id){
this.id = id;
}
@Override
public int getValue() {
return id;
}
}

View File

@@ -0,0 +1,7 @@
package emu.grasscutter.game.dungeons.enums;
public enum DungeonPlayType {
DUNGEON_PLAY_TYPE_NONE,
DUNGEON_PLAY_TYPE_FOGGY_MAZE,
DUNGEON_PLAY_TYPE_MIST_TRIAL,
DUNGEON_PLAY_TYPE_TRIAL_AVATAR
}

View File

@@ -0,0 +1,9 @@
package emu.grasscutter.game.dungeons.enums;
public enum DungeonSubType {
DUNGEON_SUB_NONE,
DUNGEON_SUB_BOSS,
DUNGEON_SUB_TALENT,
DUNGEON_SUB_WEAPON,
DUNGEON_SUB_RELIQUARY
}

View File

@@ -0,0 +1,49 @@
package emu.grasscutter.game.dungeons.enums;
import lombok.Getter;
public enum DungeonType {
DUNGEON_NONE(false),
DUNGEON_PLOT(true),
DUNGEON_FIGHT(true),
DUNGEON_DAILY_FIGHT(false),
DUNGEON_WEEKLY_FIGHT(true),
DUNGEON_DISCARDED(false),
DUNGEON_TOWER(false),
DUNGEON_BOSS(true),
DUNGEON_ACTIVITY(false),
DUNGEON_EFFIGY(false),
DUNGEON_ELEMENT_CHALLENGE(true),
DUNGEON_THEATRE_MECHANICUS(false),
DUNGEON_FLEUR_FAIR(false),
DUNGEON_CHANNELLER_SLAB_LOOP(false),
DUNGEON_CHANNELLER_SLAB_ONE_OFF(false),
DUNGEON_BLITZ_RUSH(true),
DUNGEON_CHESS(false),
DUNGEON_SUMO_COMBAT(false),
DUNGEON_ROGUELIKE(false),
DUNGEON_HACHI(false),
DUNGEON_POTION(false),
DUNGEON_MINI_ELDRITCH(false),
DUNGEON_UGC(false),
DUNGEON_GCG(false),
DUNGEON_CRYSTAL_LINK(false),
DUNGEON_IRODORI_CHESS(false),
DUNGEON_ROGUE_DIARY(false),
DUNGEON_DREAMLAND(false),
DUNGEON_SUMMER_V2(true),
DUNGEON_MUQADAS_POTION(false),
DUNGEON_INSTABLE_SPRAY(false),
DUNGEON_WIND_FIELD(false),
DUNGEON_BIGWORLD_MIRROR(false),
DUNGEON_FUNGUS_FIGHTER_TRAINING(false),
DUNGEON_FUNGUS_FIGHTER_PLOT(false),
DUNGEON_EFFIGY_CHALLENGE_V2(false),
DUNGEON_CHAR_AMUSEMENT(false);
@Getter private final boolean countsToBattlepass;
DungeonType(boolean countsToBattlepass){
this.countsToBattlepass = countsToBattlepass;
}
}

View File

@@ -0,0 +1,12 @@
package emu.grasscutter.game.dungeons.enums;
public enum DungunEntryType {
DUNGEN_ENTRY_TYPE_NONE ,
DUNGEN_ENTRY_TYPE_AVATAR_EXP ,
DUNGEN_ENTRY_TYPE_WEAPON_PROMOTE,
DUNGEN_ENTRY_TYPE_AVATAR_TALENT ,
DUNGEN_ENTRY_TYPE_RELIQUARY ,
DUNGEN_ENTRY_TYPE_SCOIN ,
DUNGEON_ENTRY_TYPE_OBSCURAE ,
DUNGEON_ENTRY_TYPE_NORMAL
}

View File

@@ -0,0 +1,17 @@
package emu.grasscutter.game.dungeons.enums;
import lombok.Getter;
public enum SettleShowType {
SETTLE_SHOW_NONE(0),
SETTLE_SHOW_TIME_COST(1),
SETTLE_SHOW_OPEN_CHEST_COUNT(2),
SETTLE_SHOW_KILL_MONSTER_COUNT(3),
SETTLE_SHOW_BLACKSCREEN(4);
@Getter private final int id;
SettleShowType(int id){
this.id = id;
}
}

View File

@@ -0,0 +1,9 @@
package emu.grasscutter.game.dungeons.handlers;
import emu.grasscutter.data.excels.DungeonPassConfigData;
public abstract class DungeonBaseHandler {
public abstract boolean execute(DungeonPassConfigData.DungeonPassCondition condition, int... params);
}

View File

@@ -0,0 +1,17 @@
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.handlers.DungeonBaseHandler;
@DungeonValue(DungeonPassConditionType.DUNGEON_COND_NONE)
public class BaseCondition extends DungeonBaseHandler {
@Override
public boolean execute(DungeonPassConfigData.DungeonPassCondition condition, int... params) {
// TODO Auto-generated method stub
return false;
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.handlers.DungeonBaseHandler;
@DungeonValue(DungeonPassConditionType.DUNGEON_COND_FINISH_CHALLENGE)
public class ConditionFinishChallenge extends DungeonBaseHandler {
@Override
public boolean execute(DungeonPassConfigData.DungeonPassCondition condition, int... params) {
return params[0] == condition.getParam()[0] || params[1] == condition.getParam()[0];
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.handlers.DungeonBaseHandler;
@DungeonValue(DungeonPassConditionType.DUNGEON_COND_FINISH_QUEST)
public class ConditionFinishQuest extends DungeonBaseHandler {
@Override
public boolean execute(DungeonPassConfigData.DungeonPassCondition condition, int... params) {
return params[0] == condition.getParam()[0];
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.handlers.DungeonBaseHandler;
@DungeonValue(DungeonPassConditionType.DUNGEON_COND_IN_TIME)
public class ConditionInTime extends DungeonBaseHandler {
@Override
public boolean execute(DungeonPassConfigData.DungeonPassCondition condition, int... params) {
return params[0] <= condition.getParam()[0];
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.handlers.DungeonBaseHandler;
@DungeonValue(DungeonPassConditionType.DUNGEON_COND_KILL_GROUP_MONSTER)
public class ConditionKillGroupMonster extends DungeonBaseHandler {
@Override
public boolean execute(DungeonPassConfigData.DungeonPassCondition condition, int... params) {
return params[0] == condition.getParam()[0];
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.handlers.DungeonBaseHandler;
@DungeonValue(DungeonPassConditionType.DUNGEON_COND_KILL_MONSTER)
public class ConditionKillMonster extends DungeonBaseHandler {
@Override
public boolean execute(DungeonPassConfigData.DungeonPassCondition condition, int... params) {
return params[0] == condition.getParam()[0];
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.handlers.DungeonBaseHandler;
@DungeonValue(DungeonPassConditionType.DUNGEON_COND_KILL_MONSTER_COUNT)
public class ConditionKillMonsterCount extends DungeonBaseHandler {
@Override
public boolean execute(DungeonPassConfigData.DungeonPassCondition condition, int... params) {
return params[0] >= condition.getParam()[0];
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.handlers.DungeonBaseHandler;
@DungeonValue(DungeonPassConditionType.DUNGEON_COND_KILL_TYPE_MONSTER)
public class ConditionKillTypeMonster extends DungeonBaseHandler {
@Override
public boolean execute(DungeonPassConfigData.DungeonPassCondition condition, int... params) {
return params[0] == condition.getParam()[0];
}
}

View File

@@ -0,0 +1,39 @@
package emu.grasscutter.game.entity.gadget;
import java.util.Arrays;
import emu.grasscutter.game.entity.EntityClientGadget;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.AbilityGadgetInfoOuterClass;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import lombok.val;
public class GadgetAbility extends GadgetContent {
private EntityClientGadget parent;
public GadgetAbility(EntityGadget gadget, EntityClientGadget parent) {
super(gadget);
this.parent = parent;
}
public boolean onInteract(Player player, GadgetInteractReq req) {
return false;
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {
if (this.parent == null) {
return;
}
val abilityGadgetInfo = AbilityGadgetInfoOuterClass.AbilityGadgetInfo.newBuilder()
.setCampId(parent.getCampId())
.setCampTargetType(parent.getCampType())
.setTargetEntityId(parent.getId())
.build();
gadgetInfo.setAbilityGadget(abilityGadgetInfo);
}
}

View File

@@ -0,0 +1,28 @@
package emu.grasscutter.game.entity.gadget.platform;
import emu.grasscutter.net.proto.MathQuaternionOuterClass.MathQuaternion;
import emu.grasscutter.net.proto.MovingPlatformTypeOuterClass;
import emu.grasscutter.net.proto.PlatformInfoOuterClass;
import emu.grasscutter.utils.Position;
/**
* TODO mostly hardcoded for EntitySolarIsotomaElevatorPlatform, should be more generic
*/
public class AbilityRoute extends BaseRoute {
private final Position basePosition;
public AbilityRoute(Position startRot, boolean startRoute, boolean isActive, Position basePosition) {
super(startRot, startRoute, isActive);
this.basePosition = basePosition;
}
@Override
public PlatformInfoOuterClass.PlatformInfo.Builder toProto() {
return super.toProto()
.setStartRot(MathQuaternion.newBuilder().setW(1.0F))
.setPosOffset(basePosition.toProto())
.setRotOffset(MathQuaternion.newBuilder().setW(1.0F))
.setMovingPlatformType(MovingPlatformTypeOuterClass.MovingPlatformType.MOVING_PLATFORM_TYPE_ABILITY);
}
}

View File

@@ -0,0 +1,84 @@
package emu.grasscutter.game.entity.gadget.platform;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.MathQuaternionOuterClass.MathQuaternion;
import emu.grasscutter.net.proto.PlatformInfoOuterClass.PlatformInfo;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.utils.Position;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
public abstract class BaseRoute {
@Getter @Setter private boolean isStarted;
@Getter @Setter private boolean isActive;
@Getter @Setter private Position startRot;
@Getter @Setter private int startSceneTime;
@Getter @Setter private int stopSceneTime;
BaseRoute(Position startRot, boolean isStarted, boolean isActive) {
this.startRot = startRot;
this.isStarted = isStarted;
this.isActive = isActive;
}
BaseRoute(SceneGadget gadget) {
this.startRot = gadget.rot;
this.isStarted = gadget.start_route;
this.isActive = gadget.start_route;
}
public static BaseRoute fromSceneGadget(SceneGadget sceneGadget) {
if (sceneGadget.route_id != 0) {
return new ConfigRoute(sceneGadget);
} else if (sceneGadget.is_use_point_array) {
return new PointArrayRoute(sceneGadget);
}
return null;
}
public boolean startRoute(Scene scene) {
if (this.isStarted) {
return false;
}
this.isStarted = true;
this.isActive = true;
this.startSceneTime = scene.getSceneTime()+300;
return true;
}
public boolean stopRoute(Scene scene) {
if (!this.isStarted) {
return false;
}
this.isStarted = false;
this.isActive = false;
this.startSceneTime = scene.getSceneTime();
this.stopSceneTime = scene.getSceneTime();
return true;
}
private MathQuaternion.Builder rotAsMathQuaternion() {
val result = MathQuaternion.newBuilder();
if (startRot != null) {
result.setX(startRot.getX())
.setY(startRot.getY())
.setZ(startRot.getZ());
}
return result;
}
public PlatformInfo.Builder toProto() {
val result = PlatformInfo.newBuilder()
.setIsStarted(isStarted)
.setIsActive(isActive)
.setStartRot(rotAsMathQuaternion())
.setStartSceneTime(startSceneTime);
if (!isStarted) {
result.setStopSceneTime(stopSceneTime);
}
return result;
}
}

View File

@@ -0,0 +1,30 @@
package emu.grasscutter.game.entity.gadget.platform;
import emu.grasscutter.net.proto.MovingPlatformTypeOuterClass;
import emu.grasscutter.net.proto.PlatformInfoOuterClass;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.utils.Position;
import lombok.Getter;
import lombok.Setter;
public class ConfigRoute extends BaseRoute {
@Getter @Setter private int routeId;
public ConfigRoute(SceneGadget gadget) {
super(gadget);
this.routeId = gadget.route_id;
}
public ConfigRoute(Position startRot, boolean startRoute, boolean isActive, int routeId) {
super(startRot, startRoute, isActive);
this.routeId = routeId;
}
@Override
public PlatformInfoOuterClass.PlatformInfo.Builder toProto() {
return super.toProto()
.setRouteId(routeId)
.setMovingPlatformType(MovingPlatformTypeOuterClass.MovingPlatformType.MOVING_PLATFORM_TYPE_USE_CONFIG);
}
}

View File

@@ -0,0 +1,32 @@
package emu.grasscutter.game.entity.gadget.platform;
import emu.grasscutter.net.proto.MovingPlatformTypeOuterClass;
import emu.grasscutter.net.proto.PlatformInfoOuterClass;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.utils.Position;
import lombok.Getter;
import lombok.Setter;
/**
* TODO implement point array routes, read from missing resources
*/
public class PointArrayRoute extends BaseRoute {
@Getter @Setter int currentPoint;
@Getter @Setter int pointArrayId;
public PointArrayRoute(SceneGadget gadget) {
super(gadget);
}
public PointArrayRoute(Position startRot, boolean startRoute, boolean isActive, int pointArrayId) {
super(startRot, startRoute, isActive);
this.pointArrayId = pointArrayId;
}
@Override
public PlatformInfoOuterClass.PlatformInfo.Builder toProto() {
return super.toProto()
.setMovingPlatformType(MovingPlatformTypeOuterClass.MovingPlatformType.MOVING_PLATFORM_TYPE_ROUTE);
}
}

View File

@@ -0,0 +1,63 @@
package emu.grasscutter.game.player;
import dev.morphia.annotations.Entity;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.val;
import java.util.Map;
/**
* Tracks progress the player made in the world, like obtained items, seen characters and more
*/
@Entity
public class PlayerProgress {
@Getter private Map<Integer, ItemEntry> itemHistory;
// keep track of EXEC_ADD_QUEST_PROGRESS count, will be used in CONTENT_ADD_QUEST_PROGRESS
// not sure where to put this, this should be saved to DB but not to individual quest, since
// it will be hard to loop and compare
private Map<Integer, Integer> questProgressCountMap;
public PlayerProgress(){
this.questProgressCountMap = new Int2IntOpenHashMap();
this.itemHistory = new Int2ObjectOpenHashMap<>();
}
public boolean hasPlayerObtainedItemHistorically(int itemId){
return itemHistory.containsKey(itemId);
}
public int addToItemHistory(int itemId, int count){
val itemEntry = itemHistory.computeIfAbsent(itemId, (key) -> new ItemEntry(itemId));
return itemEntry.addToObtainedCount(count);
}
public int getCurrentProgress(int progressId){
return questProgressCountMap.getOrDefault(progressId, -1);
}
public int addToCurrentProgress(int progressId, int count){
return questProgressCountMap.merge(progressId, count, Integer::sum);
}
@Entity
@NoArgsConstructor
public static class ItemEntry{
@Getter private int itemId;
@Getter @Setter private int obtainedCount;
ItemEntry(int itemId){
this.itemId = itemId;
}
int addToObtainedCount(int amount){
this.obtainedCount+=amount;
return this.obtainedCount;
}
}
}

View File

@@ -0,0 +1,43 @@
package emu.grasscutter.game.props;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
public enum RefreshType {
REFRESH_NONE (0),
REFRESH_INTERVAL (1),
REFRESH_DAILY (2),
REFRESH_WEEKlY (3),
REFRESH_DAYBEGIN_INTERVAL (4);
private final int value;
private static final Int2ObjectMap<RefreshType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, RefreshType> stringMap = new HashMap<>();
static {
Stream.of(values()).forEach(e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
private RefreshType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static RefreshType getTypeByValue(int value) {
return map.getOrDefault(value, REFRESH_NONE);
}
public static RefreshType getTypeByName(String name) {
return stringMap.getOrDefault(name, REFRESH_NONE);
}
}

View File

@@ -0,0 +1,11 @@
package emu.grasscutter.game.quest;
import emu.grasscutter.game.quest.enums.QuestCond;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface QuestValueCond {
QuestCond value();
}

View File

@@ -0,0 +1,11 @@
package emu.grasscutter.game.quest;
import emu.grasscutter.game.quest.enums.QuestContent;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface QuestValueContent {
QuestContent value();
}

View File

@@ -0,0 +1,11 @@
package emu.grasscutter.game.quest;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import emu.grasscutter.game.quest.enums.QuestExec;
@Retention(RetentionPolicy.RUNTIME)
public @interface QuestValueExec {
QuestExec value();
}

View File

@@ -0,0 +1,35 @@
package emu.grasscutter.game.quest;
import java.util.List;
import lombok.Data;
@Data
public class TeleportData {
List<TransmitPoint> transmit_points;
List<Npc> npcs;
List<Gadget> gadgets;
@Data
public static class TransmitPoint {
private int point_id;
private int scene_id;
private String pos;
}
@Data
public static class Npc {
private int data_index;
private int room_id;
private int scene_id;
private int id;
private String alias;
private String script;
private String pos;
}
@Data
public static class Gadget {
private int id;
private String pos;
}
}

View File

@@ -0,0 +1,40 @@
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import lombok.val;
public abstract class BaseConditionQuestVar extends BaseCondition {
protected abstract boolean doCompare(int variable, int cond);
@Override
public boolean execute(Player owner, QuestData questData, QuestData.QuestAcceptCondition condition, String paramStr, int... params) {
val index = condition.getParam()[0];
val targetValue = condition.getParam()[1];
val questVarValue = getQuestVar(owner, questData, index);
Grasscutter.getLogger().debug("questVar {} : {}", index, questVarValue);
if (questVarValue < 0) {
return false;
}
return doCompare(questVarValue, targetValue);
}
protected int getQuestVar(Player owner, QuestData questData, int index) {
val mainQuest = owner.getQuestManager().getMainQuestById(questData.getMainId());
if (mainQuest == null) {
Grasscutter.getLogger().debug("mainQuest for quest var not available yet");
return -1;
}
val questVars = mainQuest.getQuestVars();
if (index >= questVars.length) {
Grasscutter.getLogger().error("questVar out of bounds for {} index {} size {}", questData.getSubId(), index, questVars.length);
return -2;
}
return questVars[index];
}
}

View File

@@ -0,0 +1,20 @@
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond;
import lombok.val;
import static emu.grasscutter.game.quest.enums.QuestCond.QUEST_COND_ACTIVITY_COND;
@QuestValueCond(QUEST_COND_ACTIVITY_COND)
public class ConditionActivityCond extends BaseCondition {
@Override
public boolean execute(Player owner, QuestData questData, QuestData.QuestAcceptCondition condition, String paramStr, int... params) {
val activityCondId = condition.getParam()[0];
val targetState = condition.getParam()[1]; // only 1 for now
return owner.getActivityManager().meetsCondition(activityCondId) == (targetState == 1);
}
}

View File

@@ -0,0 +1,19 @@
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond;
import lombok.val;
import static emu.grasscutter.game.quest.enums.QuestCond.QUEST_COND_ACTIVITY_END;
@QuestValueCond(QUEST_COND_ACTIVITY_END)
public class ConditionActivityEnd extends BaseCondition {
@Override
public boolean execute(Player owner, QuestData questData, QuestData.QuestAcceptCondition condition, String paramStr, int... params) {
val activityId = condition.getParam()[0];
return owner.getActivityManager().hasActivityEnded(activityId);
}
}

View File

@@ -0,0 +1,19 @@
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond;
import lombok.val;
import static emu.grasscutter.game.quest.enums.QuestCond.QUEST_COND_ACTIVITY_OPEN;
@QuestValueCond(QUEST_COND_ACTIVITY_OPEN)
public class ConditionActivityOpen extends BaseCondition {
@Override
public boolean execute(Player owner, QuestData questData, QuestData.QuestAcceptCondition condition, String paramStr, int... params) {
val activityId = condition.getParam()[0];
return owner.getActivityManager().isActivityActive(activityId);
}
}

View File

@@ -0,0 +1,18 @@
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond;
import emu.grasscutter.game.quest.enums.QuestCond;
import lombok.val;
@QuestValueCond(QuestCond.QUEST_COND_HISTORY_GOT_ANY_ITEM)
public class ConditionHistoryGotAnyItem extends BaseCondition {
@Override
public boolean execute(Player owner, QuestData questData, QuestData.QuestAcceptCondition condition, String paramStr, int... params) {
val itemId = condition.getParam()[0];
return owner.getPlayerProgress().hasPlayerObtainedItemHistorically(itemId);
}
}

View File

@@ -0,0 +1,20 @@
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond;
import emu.grasscutter.game.quest.enums.QuestCond;
import lombok.val;
@QuestValueCond(QuestCond.QUEST_COND_IS_DAYTIME)
public class ConditionIsDaytime extends BaseCondition{
@Override
public boolean execute(Player owner, QuestData questData, QuestData.QuestAcceptCondition condition, String paramStr, int... params) {
val daytime = condition.getParam()[0] == 1;
val currentTime = owner.getWorld().getGameTimeHours();
// TODO is this the real timeframe?
return (currentTime >=6 && currentTime<=18) == daytime;
}
}

View File

@@ -0,0 +1,21 @@
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond;
import lombok.val;
import static emu.grasscutter.game.quest.enums.QuestCond.QUEST_COND_ITEM_NUM_LESS_THAN;
@QuestValueCond(QUEST_COND_ITEM_NUM_LESS_THAN)
public class ConditionItemNumLessThan extends BaseCondition {
@Override
public boolean execute(Player owner, QuestData questData, QuestData.QuestAcceptCondition condition, String paramStr, int... params) {
val itemId = condition.getParam()[0];
val amount = condition.getParam()[1];
val checkItem = owner.getInventory().getItemByGuid(itemId);
return checkItem == null || checkItem.getCount() < amount;
}
}

View File

@@ -0,0 +1,16 @@
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond;
import emu.grasscutter.game.quest.enums.QuestCond;
@QuestValueCond(QuestCond.QUEST_COND_NONE)
public class ConditionNone extends BaseCondition{
@Override
public boolean execute(Player owner, QuestData questData, QuestData.QuestAcceptCondition condition, String paramStr, int... params) {
return true;
}
}

View File

@@ -0,0 +1,19 @@
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond;
import emu.grasscutter.game.quest.enums.QuestCond;
import lombok.val;
@QuestValueCond(QuestCond.QUEST_COND_OPEN_STATE_EQUAL)
public class ConditionOpenStateEqual extends BaseCondition {
@Override
public boolean execute(Player owner, QuestData questData, QuestData.QuestAcceptCondition condition, String paramStr, int... params) {
val openStateId = condition.getParam()[0];
val requiredState = condition.getParam()[1];
return owner.getProgressManager().getOpenState(openStateId) == requiredState;
}
}

View File

@@ -0,0 +1,21 @@
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond;
import lombok.val;
import static emu.grasscutter.game.quest.enums.QuestCond.QUEST_COND_PACK_HAVE_ITEM;
@QuestValueCond(QUEST_COND_PACK_HAVE_ITEM)
public class ConditionPackHaveItem extends BaseCondition {
@Override
public boolean execute(Player owner, QuestData questData, QuestData.QuestAcceptCondition condition, String paramStr, int... params) {
val itemId = condition.getParam()[0];
val targetAmount = condition.getParam()[1];
val checkItem = owner.getInventory().getItemByGuid(itemId);
return checkItem != null && checkItem.getCount() >= targetAmount;
}
}

View File

@@ -0,0 +1,18 @@
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond;
import emu.grasscutter.game.quest.enums.QuestCond;
import lombok.val;
@QuestValueCond(QuestCond.QUEST_COND_PERSONAL_LINE_UNLOCK)
public class ConditionPersonalLineUnlock extends BaseCondition {
@Override
public boolean execute(Player owner, QuestData questData, QuestData.QuestAcceptCondition condition, String paramStr, int... params) {
val personalLineId = condition.getParam()[0];
return owner.getPersonalLineList().contains(personalLineId);
}
}

View File

@@ -0,0 +1,25 @@
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond;
import emu.grasscutter.game.quest.enums.QuestCond;
import lombok.val;
@QuestValueCond(QuestCond.QUEST_COND_TIME_VAR_GT_EQ)
public class ConditionTimeVarGreaterOrEqual extends BaseCondition{
@Override
public boolean execute(Player owner, QuestData questData, QuestData.QuestAcceptCondition condition, String paramStr, int... params) {
val mainQuestId = condition.getParam()[0];
val timeVarIndex = condition.getParam()[1];
val minTime = condition.getParam()[2];
val mainQuest = owner.getQuestManager().getMainQuestById(mainQuestId);
if(mainQuest == null){
return false;
}
return mainQuest.getHoursSinceTimeVar(timeVarIndex) >= minTime;
}
}

View File

@@ -0,0 +1,30 @@
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond;
import emu.grasscutter.game.quest.enums.QuestCond;
import lombok.val;
@QuestValueCond(QuestCond.QUEST_COND_TIME_VAR_PASS_DAY)
public class ConditionTimeVarPassDay extends BaseCondition{
@Override
public boolean execute(Player owner, QuestData questData, QuestData.QuestAcceptCondition condition, String paramStr, int... params) {
val mainQuestId = condition.getParam()[0];
val timeVarIndex = condition.getParam()[1];
val minDays = condition.getParam()[2];
val mainQuest = owner.getQuestManager().getMainQuestById(mainQuestId);
if(mainQuest == null){
return false;
}
val daysSinceTimeVar = mainQuest.getDaysSinceTimeVar(timeVarIndex);
if(daysSinceTimeVar == -1){
return false;
}
return daysSinceTimeVar >= minDays;
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_ANY_MANUAL_TRANSPORT;
@QuestValueContent(QUEST_CONTENT_ANY_MANUAL_TRANSPORT)
public class ContentAnyManualTransport extends BaseContent {
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
return true;
}
}

View File

@@ -0,0 +1,19 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import lombok.val;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_CLEAR_GROUP_MONSTER;
@QuestValueContent(QUEST_CONTENT_CLEAR_GROUP_MONSTER)
public class ContentClearGroupMonster extends BaseContent {
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
val groupId = condition.getParam()[0];
return quest.getOwner().getScene().getScriptManager().isClearedGroupMonsters(groupId);
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_DESTROY_GADGET;
@QuestValueContent(QUEST_CONTENT_DESTROY_GADGET)
public class ContentDestroyGadget extends BaseContent {
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
return condition.getParam()[0] == params[0];
}
}

View File

@@ -0,0 +1,17 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_ENTER_MY_WORLD;
@QuestValueContent(QUEST_CONTENT_ENTER_MY_WORLD)
public class ContentEnterMyWorld extends BaseContent {
// params[0] scene ID
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
return condition.getParam()[0] == params[0];
}
}

View File

@@ -0,0 +1,17 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_ENTER_MY_WORLD_SCENE;
@QuestValueContent(QUEST_CONTENT_ENTER_MY_WORLD_SCENE)
public class ContentEnterMyWorldScene extends BaseContent {
// params[0] scene ID
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
return condition.getParam()[0] == params[0];
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_ENTER_VEHICLE;
@QuestValueContent(QUEST_CONTENT_ENTER_VEHICLE)
public class ContentEnterVehicle extends BaseContent {
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
return condition.getParam()[0] == params[0];
}
}

View File

@@ -0,0 +1,18 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_FAIL_DUNGEON;
@QuestValueContent(QUEST_CONTENT_FAIL_DUNGEON)
public class ContentFailDungeon extends BaseContent {
// params[0] dungeon ID
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
return condition.getParam()[0] == params[0];
}
}

View File

@@ -0,0 +1,18 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_FINISH_DUNGEON;
@QuestValueContent(QUEST_CONTENT_FINISH_DUNGEON)
public class ContentFinishDungeon extends BaseContent {
// params[0] dungeon ID, params[1] unknown
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
return condition.getParam()[0] == params[0];
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_ITEM_LESS_THAN;
@QuestValueContent(QUEST_CONTENT_ITEM_LESS_THAN)
public class ContentItemLessThan extends BaseContent {
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
return condition.getParam()[0] == params[0] && condition.getCount() > params[1];
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_KILL_MONSTER;
@QuestValueContent(QUEST_CONTENT_KILL_MONSTER)
public class ContentKillMonster extends BaseContent {
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
return condition.getParam()[0] == params[0];
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_MONSTER_DIE;
@QuestValueContent(QUEST_CONTENT_MONSTER_DIE)
public class ContentMonsterDie extends BaseContent {
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
return condition.getParam()[0] == params[0];
}
}

View File

@@ -0,0 +1,19 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_OBTAIN_ITEM;
@QuestValueContent(QUEST_CONTENT_OBTAIN_ITEM)
public class ContentObtainItem extends BaseContent {
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
var targetCount = condition.getCount();
if (targetCount == 0) {
targetCount = 1;
}
return condition.getParam()[0] == params[0] && targetCount <= params[1];
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_PLAYER_LEVEL_UP;
@QuestValueContent(QUEST_CONTENT_PLAYER_LEVEL_UP)
public class ContentPlayerLevelUp extends BaseContent {
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
return quest.getOwner().getLevel() >= condition.getCount();
}
}

View File

@@ -0,0 +1,25 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import emu.grasscutter.game.quest.enums.QuestContent;
import lombok.val;
@QuestValueContent(QuestContent.QUEST_CONTENT_TIME_VAR_GT_EQ)
public class ContentTimeVarMoreOrEqual extends BaseContent{
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
val mainQuestId = condition.getParam()[0];
val timeVarIndex = condition.getParam()[1];
val minTime = Integer.parseInt(condition.getParamStr());
val mainQuest = quest.getOwner().getQuestManager().getMainQuestById(mainQuestId);
if(mainQuest == null){
return false;
}
return mainQuest.getHoursSinceTimeVar(timeVarIndex) >= minTime;
}
}

View File

@@ -0,0 +1,30 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import emu.grasscutter.game.quest.enums.QuestContent;
import lombok.val;
@QuestValueContent(QuestContent.QUEST_CONTENT_TIME_VAR_PASS_DAY)
public class ContentTimeVarPassDay extends BaseContent{
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
val mainQuestId = condition.getParam()[0];
val timeVarIndex = condition.getParam()[1];
val minDays = Integer.parseInt(condition.getParamStr());
val mainQuest = quest.getOwner().getQuestManager().getMainQuestById(mainQuestId);
if(mainQuest == null){
return false;
}
val daysSinceTimeVar = mainQuest.getDaysSinceTimeVar(timeVarIndex);
if(daysSinceTimeVar == -1){
return false;
}
return daysSinceTimeVar >= minDays;
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_UNLOCK_AREA;
@QuestValueContent(QUEST_CONTENT_UNLOCK_AREA)
public class ContentUnlockArea extends BaseContent {
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
return condition.getParam()[0] == params[0] || condition.getParam()[1] == params[1];
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_USE_ITEM;
@QuestValueContent(QUEST_CONTENT_USE_ITEM)
public class ContentUseItem extends BaseContent {
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
return condition.getParam()[0] == params[0];
}
}

View File

@@ -0,0 +1,15 @@
package emu.grasscutter.game.quest.content;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueContent;
import static emu.grasscutter.game.quest.enums.QuestContent.QUEST_CONTENT_WORKTOP_SELECT;
@QuestValueContent(QUEST_CONTENT_WORKTOP_SELECT)
public class ContentWorktopSelect extends BaseContent {
@Override
public boolean execute(GameQuest quest, QuestData.QuestContentCondition condition, String paramStr, int... params) {
return condition.getParam()[0] == params[0] || condition.getParam()[1] == params[1];
}
}

View File

@@ -0,0 +1,121 @@
package emu.grasscutter.game.quest.enums;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public enum QuestCond implements QuestTrigger {
QUEST_COND_NONE (0),
QUEST_COND_STATE_EQUAL (1),
QUEST_COND_STATE_NOT_EQUAL (2),
QUEST_COND_PACK_HAVE_ITEM (3),
QUEST_COND_AVATAR_ELEMENT_EQUAL (4), // missing, currently unused
QUEST_COND_AVATAR_ELEMENT_NOT_EQUAL (5), // missing, only NPC groups
QUEST_COND_AVATAR_CAN_CHANGE_ELEMENT (6), // missing, only NPC groups
QUEST_COND_CITY_LEVEL_EQUAL_GREATER (7), // missing, currently unused
QUEST_COND_ITEM_NUM_LESS_THAN (8),
QUEST_COND_DAILY_TASK_START (9), // missing
QUEST_COND_OPEN_STATE_EQUAL (10),
QUEST_COND_DAILY_TASK_OPEN (11), // missing, only NPC groups
QUEST_COND_DAILY_TASK_REWARD_CAN_GET (12), // missing, only NPC groups/talks
QUEST_COND_DAILY_TASK_REWARD_RECEIVED (13), // missing, only NPC groups/talks
QUEST_COND_PLAYER_LEVEL_REWARD_CAN_GET (14), // missing, only NPC groups/talks
QUEST_COND_EXPLORATION_REWARD_CAN_GET (15), // missing, only NPC groups/talks
QUEST_COND_IS_WORLD_OWNER (16), // missing, only NPC groups/talks
QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER (17),
QUEST_COND_SCENE_AREA_UNLOCKED (18), // missing, only NPC groups/talks
QUEST_COND_ITEM_GIVING_ACTIVED (19), // missing
QUEST_COND_ITEM_GIVING_FINISHED (20), // missing
QUEST_COND_IS_DAYTIME (21), // only NPC groups
QUEST_COND_CURRENT_AVATAR (22), // missing
QUEST_COND_CURRENT_AREA (23), // missing
QUEST_COND_QUEST_VAR_EQUAL (24),
QUEST_COND_QUEST_VAR_GREATER (25),
QUEST_COND_QUEST_VAR_LESS (26),
QUEST_COND_FORGE_HAVE_FINISH (27), // missing, only NPC groups
QUEST_COND_DAILY_TASK_IN_PROGRESS (28), // missing
QUEST_COND_DAILY_TASK_FINISHED (29), // missing, currently unused
QUEST_COND_ACTIVITY_COND (30),
QUEST_COND_ACTIVITY_OPEN (31),
QUEST_COND_DAILY_TASK_VAR_GT (32), // missing
QUEST_COND_DAILY_TASK_VAR_EQ (33), // missing
QUEST_COND_DAILY_TASK_VAR_LT (34), // missing
QUEST_COND_BARGAIN_ITEM_GT (35), // missing, currently unused
QUEST_COND_BARGAIN_ITEM_EQ (36), // missing, currently unused
QUEST_COND_BARGAIN_ITEM_LT (37), // missing, currently unused
QUEST_COND_COMPLETE_TALK (38),
QUEST_COND_NOT_HAVE_BLOSSOM_TALK (39), // missing, only NPC groups
QUEST_COND_IS_CUR_BLOSSOM_TALK (40), // missing, only Blossom groups
QUEST_COND_QUEST_NOT_RECEIVE (41), // missing
QUEST_COND_QUEST_SERVER_COND_VALID (42), // missing, only NPC groups
QUEST_COND_ACTIVITY_CLIENT_COND (43), // missing, only NPC and Activity groups
QUEST_COND_QUEST_GLOBAL_VAR_EQUAL (44),
QUEST_COND_QUEST_GLOBAL_VAR_GREATER (45),
QUEST_COND_QUEST_GLOBAL_VAR_LESS (46),
QUEST_COND_PERSONAL_LINE_UNLOCK (47),
QUEST_COND_CITY_REPUTATION_REQUEST (48), // missing
QUEST_COND_MAIN_COOP_START (49), // missing
QUEST_COND_MAIN_COOP_ENTER_SAVE_POINT (50), // missing
QUEST_COND_CITY_REPUTATION_LEVEL (51), // missing, only NPC groups
QUEST_COND_CITY_REPUTATION_UNLOCK (52), // missing, currently unused
QUEST_COND_LUA_NOTIFY (53),
QUEST_COND_CUR_CLIMATE (54),
QUEST_COND_ACTIVITY_END (55),
QUEST_COND_COOP_POINT_RUNNING (56), // missing, currently unused
QUEST_COND_GADGET_TALK_STATE_EQUAL (57), // missing, only Gadget groups
QUEST_COND_AVATAR_FETTER_GT (58), // missing, only NPC groups/talks
QUEST_COND_AVATAR_FETTER_EQ (59), // missing, only talks
QUEST_COND_AVATAR_FETTER_LT (60), // missing, only talks
QUEST_COND_NEW_HOMEWORLD_MOUDLE_UNLOCK (61), // missing, only Gadget groups
QUEST_COND_NEW_HOMEWORLD_LEVEL_REWARD (62), // missing, only Gadget groups
QUEST_COND_NEW_HOMEWORLD_MAKE_FINISH (63), // missing, only Gadget groups
QUEST_COND_HOMEWORLD_NPC_EVENT (64), // missing, only NPC groups
QUEST_COND_TIME_VAR_GT_EQ (65), // currently unused
QUEST_COND_TIME_VAR_PASS_DAY (66),
QUEST_COND_HOMEWORLD_NPC_NEW_TALK (67), // missing, only NPC groups
QUEST_COND_PLAYER_CHOOSE_MALE (68), // missing, only talks
QUEST_COND_HISTORY_GOT_ANY_ITEM (69),
QUEST_COND_LEARNED_RECIPE (70), // missing, currently unused
QUEST_COND_LUNARITE_REGION_UNLOCKED (71), // missing, only NPC groups
QUEST_COND_LUNARITE_HAS_REGION_HINT_COUNT (72), // missing, only NPC groups
QUEST_COND_LUNARITE_COLLECT_FINISH (73), // missing, only NPC groups
QUEST_COND_LUNARITE_MARK_ALL_FINISH (74), // missing, only NPC groups
QUEST_COND_NEW_HOMEWORLD_SHOP_ITEM (75), // missing, only Gadget groups
QUEST_COND_SCENE_POINT_UNLOCK (76), // missing, only NPC groups
QUEST_COND_SCENE_LEVEL_TAG_EQ (77), // missing
QUEST_COND_PLAYER_ENTER_REGION (78), // missing
QUEST_COND_UNKNOWN (9999);
private final int value;
QuestCond(int id) {
this.value = id;
}
public int getValue() {
return value;
}
private static final Int2ObjectMap<QuestCond> contentMap = new Int2ObjectOpenHashMap<>();
private static final Map<String, QuestCond> contentStringMap = new HashMap<>();
static {
Stream.of(values())
.forEach(e -> {
contentMap.put(e.getValue(), e);
contentStringMap.put(e.name(), e);
});
}
public static QuestCond getContentTriggerByValue(int value) {
return contentMap.getOrDefault(value, QUEST_COND_NONE);
}
public static QuestCond getContentTriggerByName(String name) {
return contentStringMap.getOrDefault(name, QUEST_COND_NONE);
}
}

View File

@@ -0,0 +1,117 @@
package emu.grasscutter.game.quest.enums;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
public enum QuestContent implements QuestTrigger {
QUEST_CONTENT_NONE (0),
QUEST_CONTENT_KILL_MONSTER (1), // currently unused
QUEST_CONTENT_COMPLETE_TALK (2),
QUEST_CONTENT_MONSTER_DIE (3),
QUEST_CONTENT_FINISH_PLOT (4),
QUEST_CONTENT_OBTAIN_ITEM (5),
QUEST_CONTENT_TRIGGER_FIRE (6),
QUEST_CONTENT_CLEAR_GROUP_MONSTER (7),
QUEST_CONTENT_NOT_FINISH_PLOT (8), // missing triggers, fail
QUEST_CONTENT_ENTER_DUNGEON (9),
QUEST_CONTENT_ENTER_MY_WORLD (10),
QUEST_CONTENT_FINISH_DUNGEON (11),
QUEST_CONTENT_DESTROY_GADGET (12),
QUEST_CONTENT_OBTAIN_MATERIAL_WITH_SUBTYPE (13), // missing, finish
QUEST_CONTENT_NICK_NAME (14), // missing, currently unused
QUEST_CONTENT_WORKTOP_SELECT (15), // currently unused
QUEST_CONTENT_SEAL_BATTLE_RESULT (16), // missing, currently unused
QUEST_CONTENT_ENTER_ROOM (17),
QUEST_CONTENT_GAME_TIME_TICK (18),
QUEST_CONTENT_FAIL_DUNGEON (19),
QUEST_CONTENT_LUA_NOTIFY (20),
QUEST_CONTENT_TEAM_DEAD (21), // missing, fail
QUEST_CONTENT_COMPLETE_ANY_TALK (22),
QUEST_CONTENT_UNLOCK_TRANS_POINT (23),
QUEST_CONTENT_ADD_QUEST_PROGRESS (24),
QUEST_CONTENT_INTERACT_GADGET (25),
QUEST_CONTENT_DAILY_TASK_COMP_FINISH (26), // missing, currently unused
QUEST_CONTENT_FINISH_ITEM_GIVING (27), // missing, finish
QUEST_CONTENT_SKILL (107),
QUEST_CONTENT_CITY_LEVEL_UP (109), // missing, finish
QUEST_CONTENT_PATTERN_GROUP_CLEAR_MONSTER (110), // missing, finish, for random quests
QUEST_CONTENT_ITEM_LESS_THAN (111),
QUEST_CONTENT_PLAYER_LEVEL_UP (112),
QUEST_CONTENT_DUNGEON_OPEN_STATUE (113), // missing, currently unused
QUEST_CONTENT_UNLOCK_AREA (114), // currently unused
QUEST_CONTENT_OPEN_CHEST_WITH_GADGET_ID (115), // missing, currently unused
QUEST_CONTENT_UNLOCK_TRANS_POINT_WITH_TYPE (116), // missing, currently unused
QUEST_CONTENT_FINISH_DAILY_DUNGEON (117), // missing, currently unused
QUEST_CONTENT_FINISH_WEEKLY_DUNGEON (118), // missing, currently unused
QUEST_CONTENT_QUEST_VAR_EQUAL (119),
QUEST_CONTENT_QUEST_VAR_GREATER (120),
QUEST_CONTENT_QUEST_VAR_LESS (121),
QUEST_CONTENT_OBTAIN_VARIOUS_ITEM (122), // missing, finish
QUEST_CONTENT_FINISH_TOWER_LEVEL (123), // missing, currently unused
QUEST_CONTENT_BARGAIN_SUCC (124), // missing, finish
QUEST_CONTENT_BARGAIN_FAIL (125),// missing, fail
QUEST_CONTENT_ITEM_LESS_THAN_BARGAIN (126),// missing, fail
QUEST_CONTENT_ACTIVITY_TRIGGER_FAILED (127),// missing, fail
QUEST_CONTENT_MAIN_COOP_ENTER_SAVE_POINT (128),// missing, finish
QUEST_CONTENT_ANY_MANUAL_TRANSPORT (129),
QUEST_CONTENT_USE_ITEM (130),
QUEST_CONTENT_MAIN_COOP_ENTER_ANY_SAVE_POINT (131),// missing, finish and fail
QUEST_CONTENT_ENTER_MY_HOME_WORLD (132),// missing, finish and fail
QUEST_CONTENT_ENTER_MY_WORLD_SCENE (133),// missing, finish
QUEST_CONTENT_TIME_VAR_GT_EQ (134),
QUEST_CONTENT_TIME_VAR_PASS_DAY (135),
QUEST_CONTENT_QUEST_STATE_EQUAL (136),
QUEST_CONTENT_QUEST_STATE_NOT_EQUAL (137),
QUEST_CONTENT_UNLOCKED_RECIPE (138),// missing, finish
QUEST_CONTENT_NOT_UNLOCKED_RECIPE (139),// missing, finish
QUEST_CONTENT_FISHING_SUCC (140),// missing, finish
QUEST_CONTENT_ENTER_ROGUE_DUNGEON (141),// missing, finish
QUEST_CONTENT_USE_WIDGET (142),// missing, finish, only in unreleased quest
QUEST_CONTENT_CAPTURE_SUCC (143),// missing, currently unused
QUEST_CONTENT_CAPTURE_USE_CAPTURETAG_LIST (144),// missing, currently unused
QUEST_CONTENT_CAPTURE_USE_MATERIAL_LIST (145),// missing, finish
QUEST_CONTENT_ENTER_VEHICLE (147),
QUEST_CONTENT_SCENE_LEVEL_TAG_EQ (148),// missing, finish
QUEST_CONTENT_LEAVE_SCENE (149),
QUEST_CONTENT_LEAVE_SCENE_RANGE (150),// missing, fail
QUEST_CONTENT_IRODORI_FINISH_FLOWER_COMBINATION (151),// missing, finish
QUEST_CONTENT_IRODORI_POETRY_REACH_MIN_PROGRESS (152),// missing, finish
QUEST_CONTENT_IRODORI_POETRY_FINISH_FILL_POETRY (153),// missing, finish
QUEST_CONTENT_ACTIVITY_TRIGGER_UPDATE(154), // missing
QUEST_CONTENT_GADGET_STATE_CHANGE(155), // missing
QUEST_CONTENT_UNKNOWN (9999);
private final int value;
QuestContent(int id) {
this.value = id;
}
public int getValue() {
return value;
}
private static final Int2ObjectMap<QuestContent> contentMap = new Int2ObjectOpenHashMap<>();
private static final Map<String, QuestContent> contentStringMap = new HashMap<>();
static {
Stream.of(values())
.forEach(e -> {
contentMap.put(e.getValue(), e);
contentStringMap.put(e.name(), e);
});
}
public static QuestContent getContentTriggerByValue(int value) {
return contentMap.getOrDefault(value, QUEST_CONTENT_NONE);
}
public static QuestContent getContentTriggerByName(String name) {
return contentStringMap.getOrDefault(name, QUEST_CONTENT_NONE);
}
}

Some files were not shown because too many files have changed in this diff Show More