Copy some files from Grasscutter-Quests

NOT completely finished, nor is it completely done. Protocol issues remain! (including lack of packet IDs)
This commit is contained in:
KingRainbow44
2023-04-01 18:06:30 -04:00
parent 262ee38ded
commit daa51e53b7
381 changed files with 10285 additions and 9150 deletions

View File

@@ -9,6 +9,7 @@ import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.entity.gadget.GadgetGatherObject;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.net.proto.AbilityInvokeEntryHeadOuterClass.AbilityInvokeEntryHead;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.AbilityMetaModifierChangeOuterClass.AbilityMetaModifierChange;
@@ -39,6 +40,7 @@ public final class AbilityManager extends BasePlayerManager {
case ABILITY_INVOKE_ARGUMENT_META_MODIFIER_CHANGE -> this.handleModifierChange(invoke);
case ABILITY_INVOKE_ARGUMENT_MIXIN_COST_STAMINA -> this.handleMixinCostStamina(invoke);
case ABILITY_INVOKE_ARGUMENT_ACTION_GENERATE_ELEM_BALL -> this.handleGenerateElemBall(invoke);
case ABILITY_INVOKE_ARGUMENT_META_GLOBAL_FLOAT_VALUE -> this.handleGlobalFloatValue(invoke);
default -> {}
}
}
@@ -202,6 +204,22 @@ public final class AbilityManager extends BasePlayerManager {
this.player.getEnergyManager().handleGenerateElemBall(invoke);
}
/**
* Handles a float value ability entry.
*
* @param invoke The ability invoke entry.
*/
private void handleGlobalFloatValue(AbilityInvokeEntry invoke)
throws InvalidProtocolBufferException {
var entry = AbilityScalarValueEntry.parseFrom(invoke.getAbilityData());
if (entry.getKey().hasStr()
&& entry.hasFloatValue()
&& entry.getFloatValue() == 2.0f
&& entry.getKey().getStr().equals("_ABILITY_UziExplode_Count")) {
player.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_SKILL, 10006);
}
}
private void invokeAction(
AbilityModifierAction action, GameEntity target, GameEntity sourceEntity) {
switch (action.type) {

View File

@@ -6,7 +6,7 @@ import dev.morphia.annotations.Id;
import dev.morphia.annotations.Transient;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AchievementData;
import emu.grasscutter.data.excels.achievement.AchievementData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;

View File

@@ -14,7 +14,19 @@ public class ActivityConfigItem {
int scheduleId;
List<Integer> meetCondList;
Date beginTime;
Date openTime;
Date closeTime;
Date endTime;
transient ActivityHandler activityHandler;
void onLoad() {
if (openTime == null) {
this.openTime = beginTime;
}
if (closeTime == null) {
this.closeTime = endTime;
}
}
}

View File

@@ -2,9 +2,12 @@ package emu.grasscutter.game.activity;
import com.esotericsoftware.reflectasm.ConstructorAccess;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.ActivityData;
import emu.grasscutter.data.excels.activity.ActivityData;
import emu.grasscutter.data.server.ActivityCondGroup;
import emu.grasscutter.game.activity.condition.ActivityConditionExecutor;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.quest.enums.QuestCond;
import emu.grasscutter.net.proto.ActivityInfoOuterClass;
import emu.grasscutter.utils.DateHelper;
import java.util.*;
@@ -19,9 +22,9 @@ import lombok.experimental.FieldDefaults;
@FieldDefaults(level = AccessLevel.PRIVATE)
public abstract class ActivityHandler {
/** Must set before initWatchers */
ActivityConfigItem activityConfigItem;
@Getter ActivityConfigItem activityConfigItem;
ActivityData activityData;
@Getter ActivityData activityData;
Map<WatcherTriggerType, List<ActivityWatcher>> watchersMap = new HashMap<>();
public abstract void onProtoBuild(
@@ -57,6 +60,43 @@ public abstract class ActivityHandler {
});
}
protected void triggerCondEvents(Player player) {
if (activityData == null) {
return;
}
var questManager = player.getQuestManager();
activityData
.getCondGroupId()
.forEach(
condGroupId -> {
var condGroup = GameData.getActivityCondGroupMap().get((int) condGroupId);
condGroup
.getCondIds()
.forEach(
condition ->
questManager.queueEvent(QuestCond.QUEST_COND_ACTIVITY_COND, condition));
});
}
private List<Integer> getActivityConditions() {
if (activityData == null) {
return new ArrayList<>();
}
return activityData.getCondGroupId().stream()
.map(condGroupId -> GameData.getActivityCondGroupMap().get((int) condGroupId))
.filter(Objects::nonNull)
.map(ActivityCondGroup::getCondIds)
.flatMap(Collection::stream)
.toList();
}
// TODO handle possible overwrites
private List<Integer> getMeetConditions(ActivityConditionExecutor conditionExecutor) {
return conditionExecutor.getMeetActivitiesConditions(getActivityConditions());
}
private Map<Integer, PlayerActivityData.WatcherInfo> initWatchersDataForPlayer() {
return watchersMap.values().stream()
.flatMap(Collection::stream)
@@ -76,7 +116,8 @@ public abstract class ActivityHandler {
return playerActivityData;
}
public ActivityInfoOuterClass.ActivityInfo toProto(PlayerActivityData playerActivityData) {
public ActivityInfoOuterClass.ActivityInfo toProto(
PlayerActivityData playerActivityData, ActivityConditionExecutor conditionExecutor) {
var proto = ActivityInfoOuterClass.ActivityInfo.newBuilder();
proto
.setActivityId(activityConfigItem.getActivityId())
@@ -85,7 +126,7 @@ public abstract class ActivityHandler {
.setBeginTime(DateHelper.getUnixTime(activityConfigItem.getBeginTime()))
.setFirstDayStartTime(DateHelper.getUnixTime(activityConfigItem.getBeginTime()))
.setEndTime(DateHelper.getUnixTime(activityConfigItem.getEndTime()))
.addAllMeetCondList(activityConfigItem.getMeetCondList());
.addAllMeetCondList(getMeetConditions(conditionExecutor));
if (playerActivityData != null) {
proto.addAllWatcherInfoList(playerActivityData.getAllWatcherInfoList());

View File

@@ -4,6 +4,7 @@ import com.esotericsoftware.reflectasm.ConstructorAccess;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.activity.condition.*;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActivityType;
@@ -12,6 +13,7 @@ import emu.grasscutter.net.proto.ActivityInfoOuterClass;
import emu.grasscutter.server.packet.send.PacketActivityScheduleInfoNotify;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import lombok.Getter;
import org.reflections.Reflections;
@@ -19,6 +21,8 @@ import org.reflections.Reflections;
public class ActivityManager extends BasePlayerManager {
private static final Map<Integer, ActivityConfigItem> activityConfigItemMap;
@Getter private static final Map<Integer, ActivityConfigItem> scheduleActivityConfigMap;
private final Map<Integer, PlayerActivityData> playerActivityDataMap;
private final ActivityConditionExecutor conditionExecutor;
static {
activityConfigItemMap = new HashMap<>();
@@ -26,30 +30,6 @@ public class ActivityManager extends BasePlayerManager {
loadActivityConfigData();
}
private final Map<Integer, PlayerActivityData> playerActivityDataMap;
public ActivityManager(Player player) {
super(player);
playerActivityDataMap = new ConcurrentHashMap<>();
// load data for player
activityConfigItemMap
.values()
.forEach(
item -> {
var data = PlayerActivityData.getByPlayer(player, item.getActivityId());
if (data == null) {
data = item.getActivityHandler().initPlayerActivityData(player);
data.save();
}
data.setPlayer(player);
data.setActivityHandler(item.getActivityHandler());
playerActivityDataMap.put(item.getActivityId(), data);
});
player.sendPacket(new PacketActivityScheduleInfoNotify(activityConfigItemMap.values()));
}
private static void loadActivityConfigData() {
// scan activity type handler & watcher type
var activityHandlerTypeMap = new HashMap<ActivityType, ConstructorAccess<?>>();
@@ -75,6 +55,7 @@ public class ActivityManager extends BasePlayerManager {
DataLoader.loadList("ActivityConfig.json", ActivityConfigItem.class)
.forEach(
item -> {
item.onLoad();
var activityData = GameData.getActivityDataMap().get(item.getActivityId());
if (activityData == null) {
Grasscutter.getLogger().warn("activity {} not exist.", item.getActivityId());
@@ -104,6 +85,36 @@ public class ActivityManager extends BasePlayerManager {
}
}
public ActivityManager(Player player) {
super(player);
playerActivityDataMap = new ConcurrentHashMap<>();
// load data for player
activityConfigItemMap
.values()
.forEach(
item -> {
var data = PlayerActivityData.getByPlayer(player, item.getActivityId());
if (data == null) {
data = item.getActivityHandler().initPlayerActivityData(player);
data.save();
}
data.setPlayer(player);
data.setActivityHandler(item.getActivityHandler());
playerActivityDataMap.put(item.getActivityId(), data);
});
player.sendPacket(new PacketActivityScheduleInfoNotify(activityConfigItemMap.values()));
conditionExecutor =
new BasicActivityConditionExecutor(
activityConfigItemMap,
GameData.getActivityCondExcelConfigDataMap(),
PlayerActivityDataMappingBuilder.buildPlayerActivityDataByActivityCondId(
playerActivityDataMap),
AllActivityConditionBuilder.buildActivityConditions());
}
/** trigger activity watcher */
public void triggerWatcher(WatcherTriggerType watcherTriggerType, String... params) {
var watchers =
@@ -124,11 +135,71 @@ public class ActivityManager extends BasePlayerManager {
params));
}
public boolean isActivityActive(int activityId) {
var activityConfig = activityConfigItemMap.get(activityId);
if (activityConfig == null) {
return false;
}
var now = new Date();
return now.after(activityConfig.getBeginTime()) && now.before(activityConfig.getEndTime());
}
public boolean hasActivityEnded(int activityId) {
var activityConfig = activityConfigItemMap.get(activityId);
if (activityConfig == null) {
return true;
}
return new Date().after(activityConfig.getEndTime());
}
public boolean isActivityOpen(int activityId) {
var activityConfig = activityConfigItemMap.get(activityId);
if (activityConfig == null) {
return false;
}
var now = new Date();
return now.after(activityConfig.getOpenTime()) && now.before(activityConfig.getCloseTime());
}
public int getOpenDay(int activityId) {
var activityConfig = activityConfigItemMap.get(activityId);
if (activityConfig == null) {
return 0;
}
var now = new Date();
return (int)
TimeUnit.DAYS.convert(
now.getTime() - activityConfig.getOpenTime().getTime(), TimeUnit.MILLISECONDS)
+ 1;
}
public boolean isActivityClosed(int activityId) {
var activityConfig = activityConfigItemMap.get(activityId);
if (activityConfig == null) {
return false;
}
var now = new Date();
return now.after(activityConfig.getCloseTime());
}
public boolean meetsCondition(int activityCondId) {
return conditionExecutor.meetsCondition(activityCondId);
}
public void triggerActivityConditions() {
activityConfigItemMap.forEach((k, v) -> v.getActivityHandler().triggerCondEvents(player));
}
public ActivityInfoOuterClass.ActivityInfo getInfoProtoByActivityId(int activityId) {
var activityHandler = activityConfigItemMap.get(activityId).getActivityHandler();
var activityData = playerActivityDataMap.get(activityId);
return activityHandler.toProto(activityData);
return activityHandler.toProto(activityData, conditionExecutor);
}
public Optional<ActivityHandler> getActivityHandler(ActivityType type) {
@@ -138,7 +209,6 @@ public class ActivityManager extends BasePlayerManager {
.findFirst();
}
@SuppressWarnings("unchecked")
public <T extends ActivityHandler> Optional<T> getActivityHandlerAs(
ActivityType type, Class<T> clazz) {
return getActivityHandler(type).map(x -> (T) x);

View File

@@ -1,6 +1,6 @@
package emu.grasscutter.game.activity;
import emu.grasscutter.data.excels.ActivityWatcherData;
import emu.grasscutter.data.excels.activity.ActivityWatcherData;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

View File

@@ -5,7 +5,7 @@ import dev.morphia.annotations.Id;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ActivityWatcherData;
import emu.grasscutter.data.excels.activity.ActivityWatcherData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;

View File

@@ -1,17 +1,16 @@
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();
}
package emu.grasscutter.game.activity.condition;
import emu.grasscutter.data.excels.activity.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

@@ -1,21 +1,23 @@
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);
}
package emu.grasscutter.game.activity.condition;
import emu.grasscutter.data.excels.activity.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

@@ -1,10 +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);
}
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

@@ -1,51 +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
}
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

@@ -1,60 +1,63 @@
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;
}
}
package emu.grasscutter.game.activity.condition;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.excels.activity.ActivityCondExcelConfigData;
import java.util.AbstractMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.reflections.Reflections;
/**
* 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
*/
public static 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

@@ -1,71 +1,75 @@
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);
}
}
package emu.grasscutter.game.activity.condition;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.excels.activity.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

@@ -1,80 +1,81 @@
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;
}
}
}
package emu.grasscutter.game.activity.condition;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.activity.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>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>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>
* </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

@@ -1,16 +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_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
}
}
package emu.grasscutter.game.activity.condition.all;
import static emu.grasscutter.game.activity.condition.ActivityConditions.NEW_ACTIVITY_COND_DAYS_LESS;
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;
@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

@@ -1,21 +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];
}
}
package emu.grasscutter.game.activity.condition.all;
import static emu.grasscutter.game.activity.condition.ActivityConditions.NEW_ACTIVITY_COND_DAYS_GREAT_EQUAL;
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;
@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

@@ -1,24 +1,25 @@
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;
}
}
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

@@ -1,22 +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 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
}
}
package emu.grasscutter.game.activity.condition.all;
import static emu.grasscutter.game.activity.condition.ActivityConditions.NEW_ACTIVITY_COND_NOT_FINISH_TALK;
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;
@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

@@ -1,17 +1,18 @@
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];
}
}
package emu.grasscutter.game.activity.condition.all;
import static emu.grasscutter.game.activity.condition.ActivityConditions.NEW_ACTIVITY_COND_PLAYER_LEVEL_GREAT_EQUAL;
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;
@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

@@ -1,23 +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 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;
}
}
package emu.grasscutter.game.activity.condition.all;
import static emu.grasscutter.game.activity.condition.ActivityConditions.NEW_ACTIVITY_COND_QUEST_FINISH;
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;
@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

@@ -1,19 +1,20 @@
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;
}
}
package emu.grasscutter.game.activity.condition.all;
import static emu.grasscutter.game.activity.condition.ActivityConditions.NEW_ACTIVITY_COND_SALESMAN_CAN_DELIVER;
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;
@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

@@ -1,18 +1,17 @@
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;
}
}
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

@@ -1,36 +1,35 @@
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);
}
}
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 java.util.stream.Stream;
import lombok.val;
@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

@@ -1,144 +1,164 @@
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())));
}
}
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.ActivityHandler;
import emu.grasscutter.game.activity.ActivityWatcher;
import emu.grasscutter.game.activity.DefaultWatcher;
import emu.grasscutter.game.activity.GameActivity;
import emu.grasscutter.game.activity.PlayerActivityData;
import emu.grasscutter.game.dungeons.DungeonTrialTeam;
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.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);
// TODO: Apply trial avatar info.
// 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 (this.getSelectedTrialAvatarIndex() <= 0) return;
player.getTeamManager().removeTrialAvatar();
this.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

@@ -1,90 +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();
}
}
}
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 java.util.List;
import java.util.stream.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
import lombok.val;
@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

@@ -9,8 +9,19 @@ import emu.grasscutter.data.binout.OpenConfigEntry;
import emu.grasscutter.data.binout.OpenConfigEntry.SkillPointModifier;
import emu.grasscutter.data.common.FightPropData;
import emu.grasscutter.data.excels.*;
import emu.grasscutter.data.excels.AvatarSkillDepotData.InherentProudSkillOpens;
import emu.grasscutter.data.excels.ItemData.WeaponProperty;
import emu.grasscutter.data.excels.avatar.AvatarData;
import emu.grasscutter.data.excels.avatar.AvatarSkillData;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData.InherentProudSkillOpens;
import emu.grasscutter.data.excels.avatar.AvatarTalentData;
import emu.grasscutter.data.excels.reliquary.ReliquaryAffixData;
import emu.grasscutter.data.excels.reliquary.ReliquaryLevelData;
import emu.grasscutter.data.excels.reliquary.ReliquaryMainPropData;
import emu.grasscutter.data.excels.reliquary.ReliquarySetData;
import emu.grasscutter.data.excels.trial.TrialAvatarTemplateData;
import emu.grasscutter.data.excels.weapon.WeaponCurveData;
import emu.grasscutter.data.excels.weapon.WeaponPromoteData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.inventory.EquipType;
@@ -25,11 +36,14 @@ import emu.grasscutter.net.proto.FetterDataOuterClass.FetterData;
import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass;
import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass.ShowAvatarInfo;
import emu.grasscutter.net.proto.ShowEquipOuterClass.ShowEquip;
import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord;
import emu.grasscutter.net.proto.TrialAvatarInfoOuterClass.TrialAvatarInfo;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.*;
import java.util.*;
import java.util.stream.Stream;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
@@ -43,7 +57,7 @@ public class Avatar {
@Id private ObjectId id;
@Indexed @Getter private int ownerId; // Id of player that this avatar belongs to
@Transient private Player owner;
@Transient @Getter private AvatarData data;
@Transient @Getter private AvatarData avatarData;
@Transient @Getter private AvatarSkillDepotData skillDepot;
@Transient @Getter private long guid; // Player unique id
@Getter private int avatarId; // Id of avatar
@@ -81,6 +95,17 @@ public class Avatar {
@Getter @Setter private int nameCardRewardId;
@Getter @Setter private int nameCardId;
// trial avatar property
@Getter @Setter private int trialAvatarId = 0;
// cannot store to db if grant reason is not integer
@Getter @Setter
private int grantReason = TrialAvatarGrantRecord.GrantReason.GRANT_REASON_INVALID.getNumber();
@Getter @Setter private int fromParentQuestId = 0;
// so far no outer class or prop value has information of this, but from packet:
// 1 = normal, 2 = trial avatar
@Getter @Setter private int avatarType = Type.NORMAL.getNumber();
@Deprecated // Do not use. Morhpia only!
public Avatar() {
this.equips = new Int2ObjectOpenHashMap<>();
@@ -100,7 +125,7 @@ public class Avatar {
this.avatarId = data.getId();
this.nameCardRewardId = data.getNameCardRewardId();
this.nameCardId = data.getNameCardId();
this.data = data;
this.avatarData = data;
this.bornTime = (int) (System.currentTimeMillis() / 1000);
this.flyCloak = 140001;
@@ -154,16 +179,12 @@ public class Avatar {
}
public ObjectId getObjectId() {
return id;
}
public AvatarData getAvatarData() {
return data;
return this.id;
}
protected void setAvatarData(AvatarData data) {
if (this.data != null) return;
this.data = data; // Used while loading this from the database
if (this.avatarData != null) return;
this.avatarData = data; // Used while loading this from the database
}
public void setOwner(Player player) {
@@ -680,7 +701,7 @@ public class Avatar {
this.skillExtraChargeMap.clear();
// Sanity checks
if (this.data == null || this.skillDepot == null) {
if (this.avatarData == null || this.skillDepot == null) {
return;
}
@@ -1001,6 +1022,186 @@ public class Avatar {
return showAvatarInfo.build();
}
/**
* Converts this avatar into a trial avatar.
*
* @param level The avatar's level.
* @param avatarId The ID of the avatar.
* @param grantReason The reason for granting the avatar.
* @param questId The ID of the quest that granted the avatar.
*/
public void setTrialAvatarInfo(
int level, int avatarId, TrialAvatarGrantRecord.GrantReason grantReason, int questId) {
this.setLevel(level);
this.setPromoteLevel(getMinPromoteLevel(level));
this.setTrialAvatarId(avatarId);
this.setGrantReason(grantReason.getNumber());
this.setFromParentQuestId(questId);
this.setAvatarType(Type.TRIAL.getNumber());
this.applyTrialSkillLevels();
this.applyTrialItems();
}
/**
* Gets the gear template based on the avatar's level.
*
* @return The avatar's template.
*/
private int getTrialTemplate() {
return this.getLevel() <= 9
? 1
: (int)
(Math.floor(this.getLevel() / 10f) * 10); // round trial level to fit template levels
}
/**
* @return The level to be used for the avatar's skills (talents).
*/
public int getTrialSkillLevel() {
// Use default data if custom data not available.
if (GameData.getTrialAvatarCustomData().isEmpty()) {
var template = getTrialTemplate(); // round trial level to fit template levels
var templateData = GameData.getTrialAvatarTemplateDataMap().get(template);
return templateData == null ? 1 : templateData.getTrialAvatarSkillLevel();
}
// Use custom data.
var trialData = GameData.getTrialAvatarCustomData().get(this.getTrialAvatarId());
if (trialData == null) return 1;
return trialData.getCoreProudSkillLevel(); // enhanced version of weapon
}
/** Applies the correct skill level for the trial avatar. */
public void applyTrialSkillLevels() {
this.getSkillLevelMap()
.keySet()
.forEach(skill -> this.setSkillLevel(skill, this.getTrialSkillLevel()));
}
/**
* @return The weapon to use with the avatar.
*/
public int getTrialWeaponId() {
// Use default data if custom data not available.
if (GameData.getTrialAvatarCustomData().isEmpty()) {
if (GameData.getTrialAvatarDataMap().get(this.getTrialAvatarId()) == null)
return this.getAvatarData().getInitialWeapon();
return GameData.getItemDataMap().get(this.getAvatarData().getInitialWeapon() + 100) == null
? getAvatarData().getInitialWeapon()
: getAvatarData().getInitialWeapon() + 100; // enhanced version of weapon
}
// Use custom data.
var trialData = GameData.getTrialAvatarCustomData().get(this.getTrialAvatarId());
if (trialData == null) return 0;
var trialCustomParams = trialData.getTrialAvatarParamList();
return trialCustomParams.size() < 2
? getAvatarData().getInitialWeapon()
: Integer.parseInt(trialCustomParams.get(1).split(";")[0]);
}
/**
* @return A list of artifact IDs to use with the avatar.
*/
public List<Integer> getTrialReliquary() {
// Use default data if custom data not available.
if (GameData.getTrialAvatarCustomData().isEmpty()) {
int trialAvatarTemplateLevel = getTrialTemplate();
TrialAvatarTemplateData templateData =
GameData.getTrialAvatarTemplateDataMap().get(trialAvatarTemplateLevel);
return templateData == null ? List.of() : templateData.getTrialReliquaryList();
}
// Use custom data.
var trialData = GameData.getTrialAvatarCustomData().get(this.getTrialAvatarId());
if (trialData == null) return List.of();
var trialCustomParams =
GameData.getTrialAvatarCustomData().get(getTrialAvatarId()).getTrialAvatarParamList();
return trialCustomParams.size() < 3
? List.of()
: Stream.of(trialCustomParams.get(2).split(";")).map(Integer::parseInt).toList();
}
/** Applies the correct items for the trial avatar. */
public void applyTrialItems() {
// Use an enhanced version of the weapon if available.
var weapon = new GameItem(this.getTrialWeaponId());
weapon.setLevel(this.getLevel());
weapon.setExp(0);
weapon.setPromoteLevel(getMinPromoteLevel(this.getLevel()));
this.getEquips().put(weapon.getEquipSlot(), weapon);
// Add artifacts for the trial avatar.
this.getTrialReliquary()
.forEach(
id -> {
var reliquaryData = GameData.getTrialReliquaryDataMap().get((int) id);
if (reliquaryData == null) return;
var relic = new GameItem(reliquaryData.getReliquaryId());
relic.setLevel(reliquaryData.getLevel());
relic.setMainPropId(reliquaryData.getMainPropId());
relic.getAppendPropIdList().addAll(reliquaryData.getAppendPropList());
this.getEquips().put(relic.getEquipSlot(), relic);
});
// Add costume if avatar has a costume.
GameData.getAvatarCostumeDataItemIdMap()
.values()
.forEach(
costumeData -> {
if (costumeData.getCharacterId() != this.getAvatarId()) return;
this.setCostume(costumeData.getId());
});
}
/** Equips the items applied from {@link Avatar#applyTrialItems()}. */
public void equipTrialItems() {
var player = this.getPlayer();
this.getEquips()
.values()
.forEach(
item -> {
item.setEquipCharacter(this.getAvatarId());
item.setOwner(player);
if (item.getItemData().getEquipType() == EquipType.EQUIP_WEAPON) {
item.setWeaponEntityId(player.getWorld().getNextEntityId(EntityIdType.WEAPON));
player.sendPacket(new PacketAvatarEquipChangeNotify(this, item));
}
});
}
/**
* Converts this (trial) avatar into a trial info protocol buffer.
*
* @return The trial info protocol buffer.
*/
public TrialAvatarInfo toTrialInfo() {
var trialAvatar =
TrialAvatarInfo.newBuilder()
.setTrialAvatarId(this.getTrialAvatarId())
.setGrantRecord(
TrialAvatarGrantRecord.newBuilder()
.setGrantReason(this.getGrantReason())
.setFromParentQuestId(this.getFromParentQuestId()));
// Check if the avatar is a trial avatar.
if (this.getTrialAvatarId() > 0) {
// Add the artifacts & weapons for the avatar.
trialAvatar.addAllTrialEquipList(
this.getEquips().values().stream().map(GameItem::toProto).toList());
}
return trialAvatar.build();
}
@PostLoad
private void onLoad() {}
@@ -1008,4 +1209,13 @@ public class Avatar {
private void prePersist() {
this.currentHp = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
}
@AllArgsConstructor
@Getter
enum Type {
NORMAL(0),
TRIAL(1);
final int number;
}
}

View File

@@ -1,8 +1,8 @@
package emu.grasscutter.game.avatar;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AvatarData;
import emu.grasscutter.data.excels.AvatarSkillDepotData;
import emu.grasscutter.data.excels.avatar.AvatarData;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.inventory.GameItem;

View File

@@ -1,14 +1,14 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify;
import emu.grasscutter.utils.Utils;
public class BasicDungeonSettleListener implements DungeonSettleListener {
@Override
public void onDungeonSettle(Scene scene) {
scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000);
scene.broadcastPacket(new PacketDungeonSettleNotify(scene.getChallenge()));
}
}
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify;
import emu.grasscutter.utils.Utils;
public class BasicDungeonSettleListener implements DungeonSettleListener {
@Override
public void onDungeonSettle(Scene scene) {
scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000);
scene.broadcastPacket(new PacketDungeonSettleNotify(scene.getChallenge()));
}
}

View File

@@ -1,18 +1,22 @@
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;
}
}
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

@@ -1,315 +1,332 @@
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);
}
}
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.dungeon.DungeonData;
import emu.grasscutter.data.excels.dungeon.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 java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.NonNull;
import lombok.val;
/**
* 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.getTeamManager().addTrialAvatars(trialTeam.trialAvatarIds);
}
}
public void unsetTrialTeam(Player player) {
if (this.trialTeam == null) {
return;
}
player.getTeamManager().removeTrialAvatar();
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

@@ -1,7 +1,7 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.world.Scene;
public interface DungeonSettleListener {
void onDungeonSettle(Scene scene);
}
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.world.Scene;
public interface DungeonSettleListener {
void onDungeonSettle(Scene scene);
}

View File

@@ -3,12 +3,8 @@ package emu.grasscutter.game.dungeons;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ScenePointEntry;
import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
import emu.grasscutter.server.game.BaseGameSystem;
@@ -18,7 +14,7 @@ import emu.grasscutter.server.packet.send.PacketPlayerEnterDungeonRsp;
import emu.grasscutter.utils.Position;
import java.util.List;
public class DungeonSystem extends BaseGameSystem {
public final class DungeonSystem extends BaseGameSystem {
private static final BasicDungeonSettleListener basicDungeonSettleObserver =
new BasicDungeonSettleListener();
@@ -27,7 +23,7 @@ public class DungeonSystem extends BaseGameSystem {
}
public void getEntryInfo(Player player, int pointId) {
ScenePointEntry entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId);
var entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId);
if (entry == null) {
// Error
@@ -39,24 +35,24 @@ public class DungeonSystem extends BaseGameSystem {
}
public boolean enterDungeon(Player player, int pointId, int dungeonId) {
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
var data = GameData.getDungeonDataMap().get(dungeonId);
if (data == null) {
return false;
}
Grasscutter.getLogger()
.info(
.debug(
"{}({}) is trying to enter dungeon {}",
player.getNickname(),
player.getUid(),
dungeonId);
int sceneId = data.getSceneId();
var sceneId = data.getSceneId();
player.getScene().setPrevScene(sceneId);
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver);
player.getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_ENTER_DUNGEON, data.getId());
player.getQuestManager().triggerEvent(QuestContent.QUEST_CONTENT_ENTER_DUNGEON, data.getId());
}
player.getScene().setPrevScenePoint(pointId);
@@ -67,13 +63,13 @@ public class DungeonSystem extends BaseGameSystem {
/** used in tower dungeons handoff */
public boolean handoffDungeon(
Player player, int dungeonId, List<DungeonSettleListener> dungeonSettleListeners) {
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
var data = GameData.getDungeonDataMap().get(dungeonId);
if (data == null) {
return false;
}
Grasscutter.getLogger()
.info(
.debug(
"{}({}) is trying to enter tower dungeon {}",
player.getNickname(),
player.getUid(),
@@ -82,30 +78,32 @@ public class DungeonSystem extends BaseGameSystem {
if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) {
dungeonSettleListeners.forEach(player.getScene()::addDungeonSettleObserver);
}
return true;
}
public void exitDungeon(Player player) {
Scene scene = player.getScene();
var scene = player.getScene();
if (scene == null || scene.getSceneType() != SceneType.SCENE_DUNGEON) {
return;
}
// Get previous scene
int prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3;
var prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3;
// Get previous position
DungeonData dungeonData = scene.getDungeonData();
Position prevPos = new Position(GameConstants.START_POSITION);
var dungeonData = scene.getDungeonData();
var prevPos = new Position(GameConstants.START_POSITION);
if (dungeonData != null) {
ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint());
var entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint());
if (entry != null) {
prevPos.set(entry.getPointData().getTranPos());
}
}
// clean temp team if it has
player.getTeamManager().cleanTemporaryTeam();
player.getTowerManager().clearEntry();
@@ -114,9 +112,4 @@ public class DungeonSystem extends BaseGameSystem {
player.getWorld().transferPlayerToScene(player, prevScene, prevPos);
player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp));
}
public void updateDailyDungeons() {
GameData.getScenePointEntries()
.forEach((id, entry) -> entry.getPointData().updateDailyDungeon());
}
}

View File

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

View File

@@ -1,11 +1,10 @@
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();
}
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

@@ -1,213 +1,55 @@
package emu.grasscutter.game.dungeons.challenge;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.DungeonData;
import emu.grasscutter.game.dungeons.DungeonDrop;
import emu.grasscutter.game.dungeons.DungeonDropEntry;
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.ResinCostTypeOuterClass;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class DungeonChallenge extends WorldChallenge {
private static final Int2ObjectMap<List<DungeonDropEntry>> dungeonDropData =
new Int2ObjectOpenHashMap<>();
/** has more challenge */
private boolean stage;
private IntSet rewardedPlayers;
public DungeonChallenge(
Scene scene,
SceneGroup group,
int challengeId,
int challengeIndex,
List<Integer> paramList,
int timeLimit,
int goal,
List<ChallengeTrigger> challengeTriggers) {
super(scene, group, challengeId, challengeIndex, paramList, timeLimit, goal, challengeTriggers);
this.setRewardedPlayers(new IntOpenHashSet());
}
public static void initialize() {
// Read the data we need for dungeon rewards drops.
try {
DataLoader.loadList("DungeonDrop.json", DungeonDrop.class)
.forEach(
entry -> {
dungeonDropData.put(entry.getDungeonId(), entry.getDrops());
});
Grasscutter.getLogger().debug("Loaded {} dungeon drop data entries.", dungeonDropData.size());
} catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load dungeon drop data.", ex);
}
}
public boolean isStage() {
return stage;
}
public void setStage(boolean stage) {
this.stage = stage;
}
public IntSet getRewardedPlayers() {
return rewardedPlayers;
}
public void setRewardedPlayers(IntSet rewardedPlayers) {
this.rewardedPlayers = rewardedPlayers;
}
@Override
public void done() {
super.done();
if (this.isSuccess()) {
// Settle
settle();
}
}
private void settle() {
if (!stage) {
var scene = this.getScene();
scene.getDungeonSettleListeners().forEach(o -> o.onDungeonSettle(getScene()));
scene
.getScriptManager()
.callEvent(EventType.EVENT_DUNGEON_SETTLE, new ScriptArgs(this.isSuccess() ? 1 : 0));
// Battle pass trigger
scene
.getPlayers()
.forEach(
p ->
p.getBattlePassManager()
.triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON));
}
}
private List<GameItem> rollRewards(boolean useCondensed) {
List<GameItem> rewards = new ArrayList<>();
int dungeonId = this.getScene().getDungeonData().getId();
// If we have specific drop data for this dungeon, we use it.
if (dungeonDropData.containsKey(dungeonId)) {
List<DungeonDropEntry> dropEntries = dungeonDropData.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 : getScene().getDungeonData().getRewardPreview().getPreviewItems()) {
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
}
}
return rewards;
}
public void getStatueDrops(Player player, GadgetInteractReq request) {
DungeonData dungeonData = getScene().getDungeonData();
int resinCost = dungeonData.getStatueCostCount() != 0 ? dungeonData.getStatueCostCount() : 20;
if (!isSuccess()
|| dungeonData == null
|| dungeonData.getRewardPreview() == null
|| dungeonData.getRewardPreview().getPreviewItems().length == 0) {
return;
}
// Already rewarded
if (getRewardedPlayers().contains(player.getUid())) {
return;
}
// Get rewards.
List<GameItem> rewards = new ArrayList<>();
if (request.getResinCostType()
== ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE) {
// 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;
}
// Spend the condensed resin and only proceed if the transaction succeeds.
if (!player.getResinManager().useCondensedResin(1)) return;
// Roll rewards.
rewards.addAll(this.rollRewards(true));
} else {
// Spend the resin and only proceed if the transaction succeeds.
if (!player.getResinManager().useResin(resinCost)) return;
// Roll rewards.
rewards.addAll(this.rollRewards(false));
}
// Add rewards to player and send notification.
player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop);
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards));
getRewardedPlayers().add(player.getUid());
}
}
package emu.grasscutter.game.dungeons.challenge;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.List;
public final class DungeonChallenge extends WorldChallenge {
/**
* has more challenge
*/
private boolean stage;
public DungeonChallenge(Scene scene, SceneGroup group,
int challengeId, int challengeIndex,
List<Integer> paramList,
int timeLimit, int goal,
List<ChallengeTrigger> challengeTriggers) {
super(scene, group, challengeId, challengeIndex, paramList, timeLimit, goal, challengeTriggers);
}
public boolean isStage() {
return stage;
}
public void setStage(boolean stage) {
this.stage = stage;
}
@Override
public void done() {
super.done();
this.getScene().triggerDungeonEvent(
DungeonPassConditionType.DUNGEON_COND_FINISH_CHALLENGE,
this.getChallengeId(), this.getChallengeIndex());
if (this.isSuccess())
this.settle();
}
private void settle() {
if (!stage) {
var scene = this.getScene();
/*if(this.isSuccess()){
scene.getDungeonManager().finishDungeon();
} else {
scene.getDungeonManager().failDungeon();
}*/
}
}
}

View File

@@ -1,19 +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
}
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

@@ -1,9 +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,
}
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

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

View File

@@ -1,27 +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,
}
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

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

View File

@@ -1,34 +1,42 @@
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)));
}
}
package emu.grasscutter.game.dungeons.challenge.factory;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_KILL_COUNT_GUARD_HP;
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 java.util.List;
import lombok.val;
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

@@ -1,32 +1,39 @@
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())
);
}
}
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 java.util.List;
import lombok.val;
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

@@ -1,33 +1,40 @@
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())
);
}
}
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 java.util.List;
import lombok.val;
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

@@ -1,35 +1,42 @@
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())
);
}
}
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 java.util.List;
import lombok.val;
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

@@ -1,33 +1,40 @@
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())
);
}
}
package emu.grasscutter.game.dungeons.challenge.factory;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_SURVIVE;
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;
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

@@ -1,36 +1,43 @@
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)))
);
}
}
package emu.grasscutter.game.dungeons.challenge.factory;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_TRIGGER_IN_TIME;
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;
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

@@ -1,13 +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();
}
}
}
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

@@ -1,23 +1,24 @@
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();
}
}
}
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

@@ -1,30 +1,31 @@
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();
}
}
}
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
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

@@ -1,74 +1,77 @@
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;
}
}
}
package emu.grasscutter.game.dungeons.dungeon_results;
import emu.grasscutter.data.excels.dungeon.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

@@ -1,50 +1,54 @@
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);
}
}
package emu.grasscutter.game.dungeons.dungeon_results;
import emu.grasscutter.data.excels.dungeon.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.DungeonSettleNotifyOuterClass;
import emu.grasscutter.net.proto.ItemParamOuterClass;
import emu.grasscutter.net.proto.TowerLevelEndNotifyOuterClass.TowerLevelEndNotify;
import emu.grasscutter.net.proto.TowerLevelEndNotifyOuterClass.TowerLevelEndNotify.ContinueStateType;
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

@@ -1,23 +1,26 @@
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));
}
}
}
package emu.grasscutter.game.dungeons.dungeon_results;
import emu.grasscutter.data.excels.dungeon.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

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

View File

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

View File

@@ -1,27 +1,28 @@
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;
}
}
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

@@ -1,7 +1,8 @@
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
}
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

@@ -1,9 +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
}
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

@@ -1,49 +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;
}
}
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

@@ -1,12 +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
}
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

@@ -1,17 +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;
}
}
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

@@ -1,9 +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);
}
package emu.grasscutter.game.dungeons.handlers;
import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData;
public abstract class DungeonBaseHandler {
public abstract boolean execute(
DungeonPassConfigData.DungeonPassCondition condition, int... params);
}

View File

@@ -1,17 +1,16 @@
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;
}
}
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
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

@@ -1,15 +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];
}
}
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
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

@@ -1,15 +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];
}
}
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
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

@@ -1,15 +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];
}
}
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
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

@@ -1,15 +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];
}
}
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
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

@@ -1,15 +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];
}
}
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
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

@@ -1,15 +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];
}
}
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
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

@@ -1,15 +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];
}
}
package emu.grasscutter.game.dungeons.pass_condition;
import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.DungeonValue;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
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

@@ -2,8 +2,8 @@ package emu.grasscutter.game.entity;
import emu.grasscutter.GameConstants;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AvatarData;
import emu.grasscutter.data.excels.AvatarSkillDepotData;
import emu.grasscutter.data.excels.avatar.AvatarData;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.inventory.EquipType;
import emu.grasscutter.game.inventory.GameItem;

View File

@@ -3,8 +3,8 @@ package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.common.PropGrowCurve;
import emu.grasscutter.data.excels.EnvAnimalGatherConfigData;
import emu.grasscutter.data.excels.MonsterCurveData;
import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.data.excels.monster.MonsterCurveData;
import emu.grasscutter.data.excels.monster.MonsterData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.*;
import emu.grasscutter.game.world.Scene;

View File

@@ -1,39 +1,37 @@
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);
}
}
package emu.grasscutter.game.entity.gadget;
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

@@ -1,28 +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);
}
}
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

@@ -1,84 +1,83 @@
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;
}
}
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

@@ -1,30 +1,31 @@
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);
}
}
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

@@ -1,32 +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);
}
}
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

@@ -5,8 +5,8 @@ import emu.grasscutter.data.GameData;
import emu.grasscutter.data.GameDepot;
import emu.grasscutter.data.common.ItemParamData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.ReliquaryAffixData;
import emu.grasscutter.data.excels.ReliquaryMainPropData;
import emu.grasscutter.data.excels.reliquary.ReliquaryAffixData;
import emu.grasscutter.data.excels.reliquary.ReliquaryMainPropData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.FightProperty;

View File

@@ -1,8 +1,8 @@
package emu.grasscutter.game.managers.blossom;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.data.excels.WorldLevelData;
import emu.grasscutter.data.excels.monster.MonsterData;
import emu.grasscutter.data.excels.world.WorldLevelData;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;

View File

@@ -6,9 +6,9 @@ import com.google.protobuf.InvalidProtocolBufferException;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AvatarSkillDepotData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.MonsterData.HpDrops;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
import emu.grasscutter.data.excels.monster.MonsterData.HpDrops;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.player.BasePlayerManager;

View File

@@ -5,7 +5,7 @@ import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.PlayerLevelData;
import emu.grasscutter.data.excels.WeatherData;
import emu.grasscutter.data.excels.world.WeatherData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.Account;
import emu.grasscutter.game.CoopRequest;
@@ -44,6 +44,7 @@ import emu.grasscutter.game.props.ClimateType;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.quest.QuestManager;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.shop.ShopLimit;
import emu.grasscutter.game.tower.TowerData;
@@ -93,235 +94,122 @@ import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
@Entity(value = "players", useDiscriminator = false)
public class Player {
private transient final Int2ObjectMap<CoopRequest> coopRequests; // Synchronized getter
@Getter
private transient final Queue<AttackResult> attackResults;
@Getter
private transient final InvokeHandler<CombatInvokeEntry> combatInvokeHandler;
@Getter
private transient final InvokeHandler<AbilityInvokeEntry> abilityInvokeHandler;
@Getter
private transient final InvokeHandler<AbilityInvokeEntry> clientAbilityInitFinishHandler;
@Id
private int id;
@Indexed(options = @IndexOptions(unique = true))
private String accountId;
@Setter
private transient Account account;
@Getter
@Setter
private transient GameSession session;
@Getter
private String nickname;
@Getter
private String signature;
@Getter
private int headImage;
@Getter
private int nameCardId = 210001;
@Getter
private final Position position;
@Getter
@Setter
private Position prevPos;
@Getter
private final Position rotation;
@Getter
private PlayerBirthday birthday;
@Getter
private PlayerCodex codex;
@Getter
@Setter
private boolean showAvatars;
@Getter
@Setter
private List<Integer> showAvatarList;
@Getter
@Setter
private List<Integer> showNameCardList;
@Getter
private final Map<Integer, Integer> properties;
@Getter
@Setter
private int currentRealmId;
@Getter
@Setter
private int widgetId;
@Getter
@Setter
private int sceneId;
@Getter
@Setter
private int regionId;
@Getter
private int mainCharacterId;
@Setter
private boolean godmode; // Getter is inGodmode
@Id private int id;
@Indexed(options = @IndexOptions(unique = true)) private String accountId;
@Setter private transient Account account;
@Getter @Setter private transient GameSession session;
@Getter private String nickname;
@Getter private String signature;
@Getter private int headImage;
@Getter private int nameCardId = 210001;
@Getter private Position position;
@Getter @Setter private Position prevPos;
@Getter private Position rotation;
@Getter private PlayerBirthday birthday;
@Getter private PlayerCodex codex;
@Getter @Setter private boolean showAvatars;
@Getter @Setter private List<Integer> showAvatarList;
@Getter @Setter private List<Integer> showNameCardList;
@Getter private Map<Integer, Integer> properties;
@Getter @Setter private int currentRealmId;
@Getter @Setter private int widgetId;
@Getter @Setter private int sceneId;
@Getter @Setter private int regionId;
@Getter private int mainCharacterId;
@Setter private boolean godmode; // Getter is inGodmode
private boolean stamina; // Getter is getUnlimitedStamina, Setter is setUnlimitedStamina
@Getter
private final Set<Integer> nameCardList;
@Getter
private final Set<Integer> flyCloakList;
@Getter
private final Set<Integer> costumeList;
@Getter
@Setter
private Set<Integer> rewardedLevels;
@Getter
@Setter
private Set<Integer> homeRewardedLevels;
@Getter
@Setter
private Set<Integer> realmList;
@Getter
@Setter
private Set<Integer> seenRealmList;
@Getter
private final Set<Integer> unlockedForgingBlueprints;
@Getter
private final Set<Integer> unlockedCombines;
@Getter
private final Set<Integer> unlockedFurniture;
@Getter
private final Set<Integer> unlockedFurnitureSuite;
@Getter
private final Map<Long, ExpeditionInfo> expeditionInfo;
@Getter
private final Map<Integer, Integer> unlockedRecipies;
@Getter
private final List<ActiveForgeData> activeForges;
@Getter
private final Map<Integer, ActiveCookCompoundData> activeCookCompounds;
@Getter
private final Map<Integer, Integer> questGlobalVariables;
@Getter
private final Map<Integer, Integer> openStates;
@Getter
@Setter
private Map<Integer, Set<Integer>> unlockedSceneAreas;
@Getter
@Setter
private Map<Integer, Set<Integer>> unlockedScenePoints;
@Getter
@Setter
private List<Integer> chatEmojiIdList;
@Transient
private long nextGuid = 0;
@Transient
@Getter
@Setter
private int peerId;
@Transient
private World world; // Synchronized getter and setter
@Transient
private Scene scene; // Synchronized getter and setter
@Transient
@Getter
private int weatherId = 0;
@Transient
@Getter
private ClimateType climate = ClimateType.CLIMATE_SUNNY;
@Getter private Set<Integer> nameCardList;
@Getter private Set<Integer> flyCloakList;
@Getter private Set<Integer> costumeList;
@Getter @Setter private Set<Integer> rewardedLevels;
@Getter @Setter private Set<Integer> homeRewardedLevels;
@Getter @Setter private Set<Integer> realmList;
@Getter @Setter private Set<Integer> seenRealmList;
@Getter private Set<Integer> unlockedForgingBlueprints;
@Getter private Set<Integer> unlockedCombines;
@Getter private Set<Integer> unlockedFurniture;
@Getter private Set<Integer> unlockedFurnitureSuite;
@Getter private Map<Long, ExpeditionInfo> expeditionInfo;
@Getter private Map<Integer, Integer> unlockedRecipies;
@Getter private List<ActiveForgeData> activeForges;
@Getter private Map<Integer, ActiveCookCompoundData> activeCookCompounds;
@Getter private Map<Integer, Integer> questGlobalVariables;
@Getter private Map<Integer, Integer> openStates;
@Getter @Setter private Map<Integer, Set<Integer>> unlockedSceneAreas;
@Getter @Setter private Map<Integer, Set<Integer>> unlockedScenePoints;
@Getter @Setter private List<Integer> chatEmojiIdList;
@Transient private long nextGuid = 0;
@Transient @Getter @Setter private int peerId;
@Transient private World world; // Synchronized getter and setter
@Transient private Scene scene; // Synchronized getter and setter
@Transient @Getter private int weatherId = 0;
@Transient @Getter private ClimateType climate = ClimateType.CLIMATE_SUNNY;
// Player managers go here
@Getter
private final transient AvatarStorage avatars;
@Getter
private final transient Inventory inventory;
@Getter
private final transient FriendsList friendsList;
@Getter
private final transient MailHandler mailHandler;
@Getter
@Setter
private transient MessageHandler messageHandler;
@Getter
private final transient AbilityManager abilityManager;
@Getter
@Setter
private transient QuestManager questManager;
@Getter
private final transient TowerManager towerManager;
@Getter
private transient SotSManager sotsManager;
@Getter
private transient MapMarksManager mapMarksManager;
@Getter
private transient StaminaManager staminaManager;
@Getter
private transient EnergyManager energyManager;
@Getter
private transient ResinManager resinManager;
@Getter
private transient ForgingManager forgingManager;
@Getter
private transient DeforestationManager deforestationManager;
@Getter
private transient FurnitureManager furnitureManager;
@Getter
private transient BattlePassManager battlePassManager;
@Getter
private transient CookingManager cookingManager;
@Getter
private transient CookingCompoundManager cookingCompoundManager;
@Getter
private transient ActivityManager activityManager;
@Getter
private final transient PlayerBuffManager buffManager;
@Getter
private transient PlayerProgressManager progressManager;
@Getter
private transient SatiationManager satiationManager;
@Getter private transient AvatarStorage avatars;
@Getter private transient Inventory inventory;
@Getter private transient FriendsList friendsList;
@Getter private transient MailHandler mailHandler;
@Getter @Setter private transient MessageHandler messageHandler;
@Getter private transient AbilityManager abilityManager;
@Getter @Setter private transient QuestManager questManager;
@Getter private transient TowerManager towerManager;
@Getter private transient SotSManager sotsManager;
@Getter private transient MapMarksManager mapMarksManager;
@Getter private transient StaminaManager staminaManager;
@Getter private transient EnergyManager energyManager;
@Getter private transient ResinManager resinManager;
@Getter private transient ForgingManager forgingManager;
@Getter private transient DeforestationManager deforestationManager;
@Getter private transient FurnitureManager furnitureManager;
@Getter private transient BattlePassManager battlePassManager;
@Getter private transient CookingManager cookingManager;
@Getter private transient CookingCompoundManager cookingCompoundManager;
@Getter private transient ActivityManager activityManager;
@Getter private transient PlayerBuffManager buffManager;
@Getter private transient PlayerProgressManager progressManager;
@Getter private transient SatiationManager satiationManager;
// Manager data (Save-able to the database)
@Getter
private transient Achievements achievements;
@Getter private transient Achievements achievements;
private PlayerProfile playerProfile; // Getter has null-check
@Getter
private TeamManager teamManager;
@Getter private TeamManager teamManager;
private TowerData towerData; // Getter has null-check
@Getter
private final PlayerGachaInfo gachaInfo;
@Getter private PlayerGachaInfo gachaInfo;
private PlayerCollectionRecords collectionRecordStore; // Getter has null-check
@Getter
private final ArrayList<ShopLimit> shopLimit;
@Getter
private transient GameHome home;
@Setter
private boolean moonCard; // Getter is inMoonCard
@Getter
@Setter
private Date moonCardStartTime;
@Getter
@Setter
private int moonCardDuration;
@Getter
@Setter
private Set<Date> moonCardGetTimes;
@Transient
@Getter
private boolean paused;
@Transient
@Getter
@Setter
private int enterSceneToken;
@Transient
@Getter
@Setter
private SceneLoadState sceneLoadState = SceneLoadState.NONE;
@Transient
private boolean hasSentLoginPackets;
@Transient
private long nextSendPlayerLocTime = 0;
@Getter
@Setter
private long springLastUsed;
@Getter private ArrayList<ShopLimit> shopLimit;
@Getter private transient GameHome home;
@Setter private boolean moonCard; // Getter is inMoonCard
@Getter @Setter private Date moonCardStartTime;
@Getter @Setter private int moonCardDuration;
@Getter @Setter private Set<Date> moonCardGetTimes;
@Transient @Getter private boolean paused;
@Transient @Getter @Setter private int enterSceneToken;
@Transient @Getter @Setter private SceneLoadState sceneLoadState = SceneLoadState.NONE;
@Transient private boolean hasSentLoginPackets;
@Transient private long nextSendPlayerLocTime = 0;
private transient final Int2ObjectMap<CoopRequest> coopRequests; // Synchronized getter
@Getter private transient final Queue<AttackResult> attackResults;
@Getter private transient final InvokeHandler<CombatInvokeEntry> combatInvokeHandler;
@Getter private transient final InvokeHandler<AbilityInvokeEntry> abilityInvokeHandler;
@Getter private transient final InvokeHandler<AbilityInvokeEntry> clientAbilityInitFinishHandler;
@Getter @Setter private long springLastUsed;
private HashMap<String, MapMark> mapMarks; // Getter makes an empty hashmap - maybe do this elsewhere?
@Getter
@Setter
private int nextResinRefresh;
@Getter
@Setter
private int lastDailyReset;
@Getter
private final transient MpSettingType mpSetting = MpSettingType.MP_SETTING_TYPE_ENTER_AFTER_APPLY; // TODO
@Getter @Setter private int nextResinRefresh;
@Getter @Setter private int lastDailyReset;
@Getter private transient MpSettingType mpSetting = MpSettingType.MP_SETTING_TYPE_ENTER_AFTER_APPLY;
@Getter private long playerGameTime = 0;
@Getter private PlayerProgress playerProgress;
@Getter private Set<Integer> activeQuestTimers;
@Deprecated
@SuppressWarnings({"rawtypes", "unchecked"}) // Morphia only!
@@ -432,6 +320,18 @@ public class Player {
this.satiationManager = new SatiationManager(this);
}
/**
* Updates the player's game time if it has changed.
*
* @param gameTime The new game time.
*/
public void updatePlayerGameTime(long gameTime) {
if (this.playerGameTime == gameTime) return;
this.playerGameTime = gameTime;
this.save();
}
public int getUid() {
return id;
}
@@ -717,7 +617,8 @@ public class Player {
// If trigger hasn't been fired yet
if (!Boolean.TRUE.equals(quest.getTriggers().put("ENTER_REGION_" + region.config_id, true))) {
//getSession().send(new PacketServerCondMeetQuestListUpdateNotify());
getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_TRIGGER_FIRE, quest.getTriggerData().get("ENTER_REGION_" + region.config_id).getId(), 0);
getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_TRIGGER_FIRE,
quest.getTriggerData().get("ENTER_REGION_" + region.config_id).getId(), 0);
}
}
});
@@ -730,7 +631,8 @@ public class Player {
// If trigger hasn't been fired yet
if (!Boolean.TRUE.equals(quest.getTriggers().put("LEAVE_REGION_" + region.config_id, true))) {
getSession().send(new PacketServerCondMeetQuestListUpdateNotify());
getQuestManager().triggerEvent(QuestTrigger.QUEST_CONTENT_TRIGGER_FIRE, quest.getTriggerData().get("LEAVE_REGION_" + region.config_id).getId(), 0);
getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_TRIGGER_FIRE,
quest.getTriggerData().get("LEAVE_REGION_" + region.config_id).getId(), 0);
}
}
});
@@ -959,6 +861,11 @@ public class Player {
this.sendPacket(new PacketSetNameCardRsp(nameCardId));
}
/**
* Sends a message to this player.
*
* @param message The message to send.
*/
public void dropMessage(Object message) {
if (this.messageHandler != null) {
this.messageHandler.append(message.toString());

View File

@@ -1,6 +1,5 @@
package emu.grasscutter.game.player;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.data.excels.BuffData;
@@ -17,13 +16,14 @@ import java.util.Objects;
import java.util.Optional;
import lombok.Getter;
public class PlayerBuffManager extends BasePlayerManager {
public final class PlayerBuffManager extends BasePlayerManager {
private final List<PlayerBuff> pendingBuffs;
private final Int2ObjectMap<PlayerBuff> buffs; // Server buffs
private int nextBuffUid;
public PlayerBuffManager(Player player) {
super(player);
this.buffs = new Int2ObjectOpenHashMap<>();
this.pendingBuffs = new ArrayList<>();
}
@@ -92,42 +92,40 @@ public class PlayerBuffManager extends BasePlayerManager {
*/
public synchronized boolean addBuff(int buffId, float duration, Avatar target) {
// Get buff excel data
BuffData buffData = GameData.getBuffDataMap().get(buffId);
var buffData = GameData.getBuffDataMap().get(buffId);
if (buffData == null) return false;
boolean success = false;
// Perform onAdded actions
success |=
var success =
Optional.ofNullable(GameData.getAbilityData(buffData.getAbilityName()))
.map(data -> data.modifiers.get(buffData.getModifierName()))
.map(modifier -> modifier.onAdded)
.map(
onAdded -> {
var s = false;
for (var a : onAdded) {
Grasscutter.getLogger().debug("onAdded exists");
if (Objects.requireNonNull(a.type) == AbilityModifierAction.Type.HealHP) {
Grasscutter.getLogger().debug("Attempting heal");
var shouldHeal = false;
for (var ability : onAdded) {
if (Objects.requireNonNull(ability.type) == AbilityModifierAction.Type.HealHP) {
if (target == null) continue;
var maxHp = target.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
var amount = a.amount.get() + a.amountByTargetMaxHPRatio.get() * maxHp;
var amount =
ability.amount.get() + ability.amountByTargetMaxHPRatio.get() * maxHp;
target.getAsEntity().heal(amount);
s = true;
Grasscutter.getLogger().debug("Healed {}", amount);
shouldHeal = true;
}
}
return s;
return shouldHeal;
})
.orElse(false);
Grasscutter.getLogger().debug("Oh no");
// Set duration
if (duration < 0f) {
duration = buffData.getTime();
}
// Dont add buff if duration is equal or less than 0
// Don't add buff if duration is equal or less than 0
if (duration <= 0) {
return success;
}

View File

@@ -3,7 +3,7 @@ package emu.grasscutter.game.player;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.CodexAnimalData;
import emu.grasscutter.data.excels.codex.CodexAnimalData;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.inventory.GameItem;

View File

@@ -1,63 +1,60 @@
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;
}
}
}
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 java.util.Map;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.val;
/** 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

@@ -6,7 +6,6 @@ import emu.grasscutter.data.excels.OpenStateData;
import emu.grasscutter.data.excels.OpenStateData.OpenStateCondType;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.packet.send.*;
import java.util.Set;
@@ -222,7 +221,7 @@ public class PlayerProgressManager extends BasePlayerDataManager {
// Fire quest trigger for trans point unlock.
this.player
.getQuestManager()
.triggerEvent(QuestTrigger.QUEST_CONTENT_UNLOCK_TRANS_POINT, sceneId, pointId);
.triggerEvent(QuestContent.QUEST_CONTENT_UNLOCK_TRANS_POINT, sceneId, pointId);
// Send packet.
this.player.sendPacket(new PacketScenePointUnlockNotify(sceneId, pointId));

View File

@@ -5,14 +5,17 @@ import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Transient;
import emu.grasscutter.GameConstants;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.AvatarSkillDepotData;
import emu.grasscutter.data.binout.config.fields.ConfigAbilityData;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.EntityBaseGadget;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.EnterReason;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.World;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.packet.PacketOpcodes;
@@ -20,18 +23,21 @@ import emu.grasscutter.net.proto.EnterTypeOuterClass.EnterType;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.PlayerDieTypeOuterClass.PlayerDieType;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord.GrantReason;
import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.server.event.player.PlayerTeamDeathEvent;
import emu.grasscutter.server.packet.send.*;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.*;
import java.util.stream.Stream;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
@Entity
public class TeamManager extends BasePlayerDataManager {
@@ -45,8 +51,16 @@ public class TeamManager extends BasePlayerDataManager {
@Getter @Setter private int currentCharacterIndex;
@Transient @Getter @Setter private TeamInfo mpTeam;
@Transient @Getter @Setter private int entityId;
@Transient private int useTemporarilyTeamIndex = -1;
@Transient private List<TeamInfo> temporaryTeam; // Temporary Team for tower
@Transient @Getter @Setter private boolean usingTrialTeam;
@Transient @Getter @Setter private TeamInfo trialAvatarTeam;
// hold trial avatars for later use in rebuilding active team
@Transient @Getter @Setter private Map<Integer, Avatar> trialAvatars;
@Transient @Getter @Setter
private int previousIndex = -1; // index of character selection in team before adding trial avatar
public TeamManager() {
this.mpTeam = new TeamInfo();
@@ -270,6 +284,18 @@ public class TeamManager extends BasePlayerDataManager {
}
}
/** Updates all properties of the active team. */
public void updateTeamProperties() {
this.updateTeamResonances(); // Update team resonances.
this.getPlayer()
.sendPacket(new PacketSceneTeamUpdateNotify(this.getPlayer())); // Notify the player.
// Skill charges packet - Yes, this is official server behavior as of 2.6.0
this.getActiveTeam().stream()
.map(EntityAvatar::getAvatar)
.forEach(Avatar::sendSkillExtraChargeMap);
}
public void updateTeamEntities(BasePacket responsePacket) {
// Sanity check - Should never happen
if (this.getCurrentTeamInfo().getAvatars().size() <= 0) {
@@ -277,9 +303,9 @@ public class TeamManager extends BasePlayerDataManager {
}
// If current team has changed
EntityAvatar currentEntity = this.getCurrentAvatarEntity();
Int2ObjectMap<EntityAvatar> existingAvatars = new Int2ObjectOpenHashMap<>();
int prevSelectedAvatarIndex = -1;
var currentEntity = this.getCurrentAvatarEntity();
var existingAvatars = new Int2ObjectOpenHashMap<EntityAvatar>();
var prevSelectedAvatarIndex = -1;
for (EntityAvatar entity : this.getActiveTeam()) {
existingAvatars.put(entity.getAvatar().getAvatarId(), entity);
@@ -290,9 +316,8 @@ public class TeamManager extends BasePlayerDataManager {
// Add back entities into team
for (int i = 0; i < this.getCurrentTeamInfo().getAvatars().size(); i++) {
int avatarId = this.getCurrentTeamInfo().getAvatars().get(i);
var avatarId = (int) this.getCurrentTeamInfo().getAvatars().get(i);
EntityAvatar entity;
if (existingAvatars.containsKey(avatarId)) {
entity = existingAvatars.get(avatarId);
existingAvatars.remove(avatarId);
@@ -309,7 +334,7 @@ public class TeamManager extends BasePlayerDataManager {
}
// Unload removed entities
for (EntityAvatar entity : existingAvatars.values()) {
for (var entity : existingAvatars.values()) {
this.getPlayer().getScene().removeEntity(entity);
entity.getAvatar().save();
}
@@ -323,18 +348,11 @@ public class TeamManager extends BasePlayerDataManager {
}
this.currentCharacterIndex = prevSelectedAvatarIndex;
// Update team resonances
this.updateTeamResonances();
// Update properties.
// Notify player.
this.updateTeamProperties();
// Packets
this.getPlayer().getWorld().broadcastPacket(new PacketSceneTeamUpdateNotify(this.getPlayer()));
// Skill charges packet - Yes, this is official server behavior as of 2.6.0
this.getActiveTeam().stream()
.map(EntityAvatar::getAvatar)
.forEach(Avatar::sendSkillExtraChargeMap);
// Run callback
// Send response packet.
if (responsePacket != null) {
this.getPlayer().sendPacket(responsePacket);
}
@@ -402,6 +420,138 @@ public class TeamManager extends BasePlayerDataManager {
this.addAvatarsToTeam(teamInfo, newTeam);
}
/**
* Setup avatars for a trial avatar team.
*
* @param save Should the original team be saved?
*/
public void setupTrialAvatars(boolean save) {
this.setPreviousIndex(this.getCurrentCharacterIndex());
if (save) {
var originalTeam = getCurrentTeamInfo();
this.getTrialAvatarTeam().copyFrom(originalTeam);
} else this.getActiveTeam().clear();
this.usingTrialTeam = true;
}
/** Displays the trial avatars. Picks the last avatar in the team. */
public void trialAvatarTeamPostUpdate() {
this.trialAvatarTeamPostUpdate(this.getActiveTeam().size() - 1);
}
/**
* Displays the trial avatars.
*
* @param newCharacterIndex The avatar to equip.
*/
public void trialAvatarTeamPostUpdate(int newCharacterIndex) {
this.setCurrentCharacterIndex(Math.min(newCharacterIndex, this.getActiveTeam().size() - 1));
this.updateTeamProperties();
this.getPlayer().getScene().addEntity(this.getCurrentAvatarEntity());
}
/**
* Adds an avatar to the trial team.
*
* @param trialAvatar The avatar to add.
*/
public void addAvatarToTrialTeam(Avatar trialAvatar) {
// Remove the existing team's avatars.
this.getActiveTeam()
.forEach(
x ->
this.getPlayer()
.getScene()
.removeEntity(x, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE));
// Remove the existing avatar from the teams if it exists.
this.getActiveTeam().removeIf(x -> x.getAvatar().getAvatarId() == trialAvatar.getAvatarId());
this.getCurrentTeamInfo().getAvatars().removeIf(x -> x == trialAvatar.getAvatarId());
// Add the avatar to the teams.
this.getActiveTeam().add(new EntityAvatar(this.getPlayer().getScene(), trialAvatar));
this.getCurrentTeamInfo().addAvatar(trialAvatar);
this.getTrialAvatars().put(trialAvatar.getAvatarId(), trialAvatar);
}
/**
* Get the GUID of a trial avatar.
*
* @param avatarId The avatar ID.
* @return The GUID of the avatar.
*/
public long getTrialAvatarGuid(int avatarId) {
return getTrialAvatars().values().stream()
.filter(avatar -> avatar.getTrialAvatarId() == avatarId)
.map(avatar -> avatar.getGuid())
.findFirst()
.orElse(0L);
}
/** Rollback changes from using a trial avatar team. */
public void unsetTrialAvatarTeam() {
this.trialAvatarTeamPostUpdate(this.getPreviousIndex());
this.setPreviousIndex(-1);
}
/** Removes all avatars from the trial avatar team. */
public void removeTrialAvatarTeam() {
this.removeTrialAvatarTeam(
this.getActiveTeam().stream().map(avatar -> avatar.getAvatar().getAvatarId()).toList());
}
/**
* Removes one avatar from the trial avatar team.
*
* @param avatarId The avatar ID to remove.
*/
public void removeTrialAvatarTeam(int avatarId) {
this.removeTrialAvatarTeam(List.of(avatarId));
}
/**
* Removes a collection of avatars from the trial avatar team.
*
* @param avatarIds The avatar IDs to remove.
*/
public void removeTrialAvatarTeam(List<Integer> avatarIds) {
var player = this.getPlayer();
// Disable the trial team.
this.usingTrialTeam = false;
this.trialAvatarTeam = new TeamInfo();
// Remove the avatars from the team.
avatarIds.forEach(
avatarId -> {
this.getActiveTeam()
.forEach(
x ->
player
.getScene()
.removeEntity(x, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE));
this.getActiveTeam().removeIf(x -> x.getAvatar().getTrialAvatarId() == avatarId);
this.getTrialAvatars().values().removeIf(x -> x.getTrialAvatarId() == avatarId);
});
// Re-add the avatars to the team.
var index = 0;
for (var avatar : this.getCurrentTeamInfo().getAvatars()) {
if (this.getActiveTeam().stream()
.map(entity -> entity.getAvatar().getAvatarId())
.toList()
.contains(avatar)) return;
this.getActiveTeam()
.add(
index++,
new EntityAvatar(player.getScene(), player.getAvatars().getAvatarById(avatar)));
}
this.unsetTrialAvatarTeam();
}
public void setupTemporaryTeam(List<List<Long>> guidList) {
this.temporaryTeam =
guidList.stream()
@@ -725,4 +875,195 @@ public class TeamManager extends BasePlayerDataManager {
player.sendPacket(new PacketAvatarTeamAllDataNotify(player));
player.sendPacket(new PacketDelBackupAvatarTeamRsp(id));
}
/**
* Applies abilities for the currently selected team. These abilities are sourced from the scene.
*
* @param scene The scene with the abilities to apply.
*/
public void applyAbilities(Scene scene) {
try {
var levelEntityConfig = scene.getSceneData().getLevelEntityConfig();
var config = GameData.getConfigLevelEntityDataMap().get(levelEntityConfig);
if (config == null) return;
var avatars = this.getPlayer().getAvatars();
var avatarIds = scene.getSceneData().getSpecifiedAvatarList();
var specifiedAvatarList = this.getActiveTeam();
if (avatarIds != null && avatarIds.size() > 0) {
// certain scene could limit specific avatars' entry
specifiedAvatarList.clear();
for (int id : avatarIds) {
var avatar = avatars.getAvatarById(id);
if (avatar == null) continue;
specifiedAvatarList.add(new EntityAvatar(scene, avatar));
}
}
for (var entityAvatar : specifiedAvatarList) {
var avatarData = entityAvatar.getAvatar().getAvatarData();
if (avatarData == null) {
continue;
}
avatarData.buildEmbryo(); // Create avatar abilities.
if (config.getAvatarAbilities() == null) {
continue; // continue and not break because has to rebuild ability for the next avatar if
// any
}
for (ConfigAbilityData abilities : config.getAvatarAbilities()) {
avatarData.getAbilities().add(Utils.abilityHash(abilities.getAbilityName()));
}
}
} catch (Exception e) {
Grasscutter.getLogger()
.error(
"Error applying level entity config for scene {}", scene.getSceneData().getId(), e);
}
}
public List<Integer> getTrialAvatarParam(int trialAvatarId) {
if (GameData.getTrialAvatarCustomData()
.isEmpty()) { // use default data if custom data not available
if (GameData.getTrialAvatarDataMap().get(trialAvatarId) == null) return List.of();
return GameData.getTrialAvatarDataMap().get(trialAvatarId).getTrialAvatarParamList();
}
// use custom data
if (GameData.getTrialAvatarCustomData().get(trialAvatarId) == null) return List.of();
val trialCustomParams =
GameData.getTrialAvatarCustomData().get(trialAvatarId).getTrialAvatarParamList();
return trialCustomParams.isEmpty()
? List.of()
: Stream.of(trialCustomParams.get(0).split(";")).map(Integer::parseInt).toList();
}
/**
* Adds a trial avatar to the player's team.
*
* @param avatarId The ID of the avatar.
* @param questMainId The quest ID associated with the quest.
* @param reason The reason for granting the avatar.
* @return True if the avatar was added, false otherwise.
*/
public boolean addTrialAvatar(int avatarId, int questMainId, GrantReason reason) {
List<Integer> trialAvatarBasicParam = getTrialAvatarParam(avatarId);
if (trialAvatarBasicParam.isEmpty()) return false;
var avatar = new Avatar(trialAvatarBasicParam.get(0));
if (avatar.getAvatarData() == null || !this.getPlayer().hasSentLoginPackets()) return false;
avatar.setOwner(this.getPlayer());
// Add trial weapons and relics.
avatar.setTrialAvatarInfo(trialAvatarBasicParam.get(1), avatarId, reason, questMainId);
avatar.equipTrialItems();
// Re-calculate stats
avatar.recalcStats();
// Packet, mimic official server behaviour, add to player's bag but not saving to database.
this.getPlayer().sendPacket(new PacketAvatarAddNotify(avatar, false));
// Add to avatar to the temporary trial team.
this.addAvatarToTrialTeam(avatar);
return true;
}
/**
* Adds a trial avatar to the player's team.
*
* @param avatarId The ID of the avatar.
* @param questMainId The quest ID associated with the quest.
*/
public void addTrialAvatar(int avatarId, int questMainId) {
this.addTrialAvatars(List.of(avatarId), questMainId, true);
// Packet, mimic official server behaviour, necessary to stop player from modifying team.
this.getPlayer().sendPacket(new PacketAvatarTeamUpdateNotify(this.getPlayer()));
}
/**
* Adds a collection of trial avatars to the player's team.
*
* @param avatarIds List of trial avatar IDs.
*/
public void addTrialAvatars(List<Integer> avatarIds) {
this.addTrialAvatars(avatarIds, 0, false);
}
/**
* Adds a collection of trial avatars to the player's team.
*
* @param avatarIds List of trial avatar IDs.
* @param save Whether to retain the currently equipped avatars.
*/
public void addTrialAvatars(List<Integer> avatarIds, boolean save) {
this.addTrialAvatars(avatarIds, 0, save);
}
/**
* Adds a list of trial avatars to the player's team.
*
* @param avatarIds List of trial avatar IDs.
* @param questId The ID of the quest this trial team is associated with.
* @param save Whether to retain the currently equipped avatars.
*/
public void addTrialAvatars(List<Integer> avatarIds, int questId, boolean save) {
this.setupTrialAvatars(save); // Perform initial setup.
// Add the avatars to the team.
avatarIds.forEach(
avatarId -> {
var result =
this.addTrialAvatar(
avatarId,
questId,
questId == 0
? GrantReason.GRANT_REASON_BY_QUEST
: GrantReason.GRANT_REASON_BY_TRIAL_AVATAR_ACTIVITY);
if (!result) throw new RuntimeException("Unable to add trial avatar to team.");
});
// Update the team.
this.trialAvatarTeamPostUpdate(questId == 0 ? getActiveTeam().size() - 1 : 0);
}
/** Removes all trial avatars from the player's team. */
public void removeTrialAvatar() {
this.removeTrialAvatar(
this.getActiveTeam().stream()
.map(EntityAvatar::getAvatar)
.map(Avatar::getAvatarId)
.toList());
}
/**
* Removes a trial avatar from the player's team. Additionally, unlocks the ability to change the
* team configuration.
*
* @param avatarId The ID of the avatar.
*/
public void removeTrialAvatar(int avatarId) {
this.removeTrialAvatar(List.of(avatarId));
}
/**
* Removes a collection of trial avatars from the player's team.
*
* @param avatarIds List of trial avatar IDs.
*/
public void removeTrialAvatar(List<Integer> avatarIds) {
if (!this.isUsingTrialTeam()) throw new RuntimeException("Player is not using a trial team.");
this.getPlayer()
.sendPacket(
new PacketAvatarDelNotify(avatarIds.stream().map(this::getTrialAvatarGuid).toList()));
this.removeTrialAvatarTeam(avatarIds);
// Update the team.
if (avatarIds.size() == 1) this.getPlayer().sendPacket(new PacketAvatarTeamUpdateNotify());
}
}

View File

@@ -12,9 +12,17 @@ import lombok.Getter;
@AllArgsConstructor
public enum ActivityType {
NONE(0),
NEW_ACTIVITY_TRIAL_AVATAR(4),
NEW_ACTIVITY_PERSONAL_LIINE(8),
NEW_ACTIVITY_SALESMAN_MP(1205),
NEW_ACTIVITY_SUMMER_TIME(1600),
NEW_ACTIVITY_GENERAL_BANNER(2100),
NEW_ACTIVITY_MUSIC_GAME(2202),
;
NEW_ACTIVITY_PHOTO(2603),
NEW_ACTIVITY_FUNGUS_FIGHTER(3201),
NEW_ACTIVITY_EFFIGY_CHALLENGE_V2(3203);
private final int value;
private static final Int2ObjectMap<ActivityType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, ActivityType> stringMap = new HashMap<>();
@@ -27,8 +35,6 @@ public enum ActivityType {
});
}
private final int value;
public static ActivityType getTypeByValue(int value) {
return map.getOrDefault(value, NONE);
}

View File

@@ -1,6 +1,6 @@
package emu.grasscutter.game.props.ItemUseAction;
import emu.grasscutter.data.excels.AvatarSkillDepotData;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;

View File

@@ -1,43 +1,44 @@
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);
}
}
package emu.grasscutter.game.props;
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 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

@@ -1,443 +1,490 @@
package emu.grasscutter.game.quest;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.Transient;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.MainQuestData;
import emu.grasscutter.data.binout.MainQuestData.SubQuestData;
import emu.grasscutter.data.binout.MainQuestData.TalkData;
import emu.grasscutter.data.binout.ScriptSceneData;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.quest.enums.LogicType;
import emu.grasscutter.game.quest.enums.ParentQuestState;
import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.net.proto.ChildQuestOuterClass.ChildQuest;
import emu.grasscutter.net.proto.ParentQuestOuterClass.ParentQuest;
import emu.grasscutter.scripts.ScriptLoader;
import emu.grasscutter.server.packet.send.PacketCodexDataUpdateNotify;
import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify;
import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify;
import emu.grasscutter.utils.Position;
import java.util.*;
import javax.script.Bindings;
import javax.script.CompiledScript;
import javax.script.ScriptException;
import lombok.Getter;
import org.bson.types.ObjectId;
@Entity(value = "quests", useDiscriminator = false)
public class GameMainQuest {
@Getter List<QuestGroupSuite> questGroupSuites;
@Getter int[] suggestTrackMainQuestList;
@Id private ObjectId id;
@Indexed @Getter private int ownerUid;
@Transient @Getter private Player owner;
@Transient @Getter private QuestManager questManager;
@Getter private Map<Integer, GameQuest> childQuests;
@Getter private int parentQuestId;
@Getter private int[] questVars;
// QuestUpdateQuestVarReq is sent in two stages...
@Getter private List<Integer> questVarsUpdate;
@Getter private ParentQuestState state;
@Getter private boolean isFinished;
@Getter private Map<Integer, TalkData> talks;
// key is subId
private Map<Integer, Position> rewindPositions;
private Map<Integer, Position> rewindRotations;
@Deprecated // Morphia only. Do not use.
public GameMainQuest() {}
public GameMainQuest(Player player, int parentQuestId) {
this.owner = player;
this.ownerUid = player.getUid();
this.questManager = player.getQuestManager();
this.parentQuestId = parentQuestId;
this.childQuests = new HashMap<>();
this.talks = new HashMap<>();
// official server always has a list of 5 questVars, with default value 0
this.questVars = new int[] {0, 0, 0, 0, 0};
this.state = ParentQuestState.PARENT_QUEST_STATE_NONE;
this.questGroupSuites = new ArrayList<>();
this.rewindPositions = new HashMap<>();
this.rewindRotations = new HashMap<>();
addAllChildQuests();
addRewindPoints();
}
private void addAllChildQuests() {
List<Integer> subQuestIds =
Arrays.stream(GameData.getMainQuestDataMap().get(this.parentQuestId).getSubQuests())
.map(SubQuestData::getSubId)
.toList();
for (Integer subQuestId : subQuestIds) {
QuestData questConfig = GameData.getQuestDataMap().get(subQuestId);
this.childQuests.put(subQuestId, new GameQuest(this, questConfig));
}
}
public void setOwner(Player player) {
if (player.getUid() != this.getOwnerUid()) return;
this.owner = player;
}
public int getQuestVar(int i) {
return questVars[i];
}
public void setQuestVar(int i, int value) {
int previousValue = this.questVars[i];
this.questVars[i] = value;
Grasscutter.getLogger()
.debug("questVar {} value changed from {} to {}", i, previousValue, value);
}
public void incQuestVar(int i, int inc) {
int previousValue = this.questVars[i];
this.questVars[i] += inc;
Grasscutter.getLogger()
.debug(
"questVar {} value incremented from {} to {}", i, previousValue, previousValue + inc);
}
public void decQuestVar(int i, int dec) {
int previousValue = this.questVars[i];
this.questVars[i] -= dec;
Grasscutter.getLogger()
.debug(
"questVar {} value decremented from {} to {}", i, previousValue, previousValue - dec);
}
public GameQuest getChildQuestById(int id) {
return this.getChildQuests().get(id);
}
public GameQuest getChildQuestByOrder(int order) {
return this.getChildQuests().values().stream()
.filter(p -> p.getQuestData().getOrder() == order)
.toList()
.get(0);
}
public void finish() {
this.isFinished = true;
this.state = ParentQuestState.PARENT_QUEST_STATE_FINISHED;
this.getOwner().getSession().send(new PacketFinishedParentQuestUpdateNotify(this));
this.getOwner().getSession().send(new PacketCodexDataUpdateNotify(this));
this.save();
// Add rewards
MainQuestData mainQuestData = GameData.getMainQuestDataMap().get(this.getParentQuestId());
if (mainQuestData != null && mainQuestData.getRewardIdList() != null) {
for (int rewardId : mainQuestData.getRewardIdList()) {
RewardData rewardData = GameData.getRewardDataMap().get(rewardId);
if (rewardData == null) {
continue;
}
getOwner()
.getInventory()
.addItemParamDatas(rewardData.getRewardItemList(), ActionReason.QuestReward);
}
}
// handoff main quest
if (mainQuestData.getSuggestTrackMainQuestList() != null) {
Arrays.stream(mainQuestData.getSuggestTrackMainQuestList())
.forEach(getQuestManager()::startMainQuest);
}
}
// TODO
public void fail() {}
public void cancel() {}
// Rewinds to the last finished/unfinished rewind quest, and returns the avatar rewind position
// (if it exists)
public List<Position> rewind() {
if (this.questManager == null) {
this.questManager = getOwner().getQuestManager();
}
List<GameQuest> sortedByOrder =
new ArrayList<>(
getChildQuests().values().stream().filter(q -> q.getQuestData().isRewind()).toList());
sortedByOrder.sort(
(a, b) -> {
if (a == b) {
return 0;
}
return a.getQuestData().getOrder() > b.getQuestData().getOrder() ? 1 : -1;
});
boolean didRewind = false;
for (GameQuest quest : sortedByOrder) {
int i = sortedByOrder.indexOf(quest);
if ((i + 1) >= sortedByOrder.size()) {
didRewind = quest.rewind(null);
} else {
didRewind = quest.rewind(sortedByOrder.get(i + 1));
}
if (didRewind) {
break;
}
}
List<GameQuest> rewindQuests =
getChildQuests().values().stream()
.filter(
p ->
(p.getState() == QuestState.QUEST_STATE_UNFINISHED
|| p.getState() == QuestState.QUEST_STATE_FINISHED)
&& p.getQuestData().isRewind())
.toList();
for (GameQuest quest : rewindQuests) {
if (rewindPositions.containsKey(quest.getSubQuestId())) {
List<Position> posAndRot = new ArrayList<>();
posAndRot.add(0, rewindPositions.get(quest.getSubQuestId()));
posAndRot.add(1, rewindRotations.get(quest.getSubQuestId()));
return posAndRot;
}
}
return null;
}
public void addRewindPoints() {
Bindings bindings = ScriptLoader.getEngine().createBindings();
String script = "Quest/Share/Q" + getParentQuestId() + "ShareConfig.lua";
CompiledScript cs = ScriptLoader.getScript(script);
// mainQuest 303 doesn't have a ShareConfig
if (cs == null) {
Grasscutter.getLogger().debug("Couldn't find " + script);
return;
}
// Eval script
try {
cs.eval(bindings);
var rewindDataMap =
ScriptLoader.getSerializer().toMap(RewindData.class, bindings.get("rewind_data"));
for (String subId : rewindDataMap.keySet()) {
RewindData questRewind = rewindDataMap.get(subId);
if (questRewind != null) {
RewindData.AvatarData avatarData = questRewind.getAvatar();
if (avatarData != null) {
String avatarPos = avatarData.getPos();
QuestData.Guide guide =
GameData.getQuestDataMap().get(Integer.valueOf(subId)).getGuide();
if (guide != null) {
int sceneId = guide.getGuideScene();
ScriptSceneData fullGlobals =
GameData.getScriptSceneDataMap().get("flat.luas.scenes.full_globals.lua.json");
if (fullGlobals != null) {
ScriptSceneData.ScriptObject dummyPointScript =
fullGlobals
.getScriptObjectList()
.get(sceneId + "/scene" + sceneId + "_dummy_points.lua");
if (dummyPointScript != null) {
Map<String, List<Float>> dummyPointMap = dummyPointScript.getDummyPoints();
if (dummyPointMap != null) {
List<Float> avatarPosPos = dummyPointMap.get(avatarPos + ".pos");
if (avatarPosPos != null) {
Position pos =
new Position(
avatarPosPos.get(0), avatarPosPos.get(1), avatarPosPos.get(2));
List<Float> avatarPosRot = dummyPointMap.get(avatarPos + ".rot");
Position rot =
new Position(
avatarPosRot.get(0), avatarPosRot.get(1), avatarPosRot.get(2));
rewindPositions.put(Integer.valueOf(subId), pos);
rewindRotations.put(Integer.valueOf(subId), rot);
Grasscutter.getLogger()
.debug("Succesfully loaded rewind position for subQuest {}", subId);
}
}
}
}
}
}
}
}
} catch (ScriptException e) {
Grasscutter.getLogger().error("An error occurred while loading rewind positions");
}
}
public void tryAcceptSubQuests(QuestTrigger condType, String paramStr, int... params) {
try {
List<GameQuest> subQuestsWithCond =
getChildQuests().values().stream()
.filter(p -> p.getState() == QuestState.QUEST_STATE_UNSTARTED)
.filter(
p ->
p.getQuestData().getAcceptCond().stream()
.anyMatch(q -> q.getType() == condType))
.toList();
for (GameQuest subQuestWithCond : subQuestsWithCond) {
List<QuestData.QuestCondition> acceptCond = subQuestWithCond.getQuestData().getAcceptCond();
int[] accept = new int[acceptCond.size()];
for (int i = 0; i < subQuestWithCond.getQuestData().getAcceptCond().size(); i++) {
QuestData.QuestCondition condition = acceptCond.get(i);
boolean result =
this.getOwner()
.getServer()
.getQuestSystem()
.triggerCondition(subQuestWithCond, condition, paramStr, params);
accept[i] = result ? 1 : 0;
}
boolean shouldAccept =
LogicType.calculate(subQuestWithCond.getQuestData().getAcceptCondComb(), accept);
if (shouldAccept) {
subQuestWithCond.start();
getQuestManager().getAddToQuestListUpdateNotify().add(subQuestWithCond);
}
}
this.save();
} catch (Exception e) {
Grasscutter.getLogger().error("An error occurred while trying to accept quest.", e);
}
}
public void tryFailSubQuests(QuestTrigger condType, String paramStr, int... params) {
try {
List<GameQuest> subQuestsWithCond =
getChildQuests().values().stream()
.filter(p -> p.getState() == QuestState.QUEST_STATE_UNFINISHED)
.filter(
p ->
p.getQuestData().getFailCond().stream()
.anyMatch(q -> q.getType() == condType))
.toList();
for (GameQuest subQuestWithCond : subQuestsWithCond) {
List<QuestData.QuestCondition> failCond = subQuestWithCond.getQuestData().getFailCond();
for (int i = 0; i < subQuestWithCond.getQuestData().getFailCond().size(); i++) {
QuestData.QuestCondition condition = failCond.get(i);
if (condition.getType() == condType) {
boolean result =
this.getOwner()
.getServer()
.getQuestSystem()
.triggerContent(subQuestWithCond, condition, paramStr, params);
subQuestWithCond.getFailProgressList()[i] = result ? 1 : 0;
if (result) {
getOwner().getSession().send(new PacketQuestProgressUpdateNotify(subQuestWithCond));
}
}
}
boolean shouldFail =
LogicType.calculate(
subQuestWithCond.getQuestData().getFailCondComb(),
subQuestWithCond.getFailProgressList());
if (shouldFail) {
subQuestWithCond.fail();
getQuestManager().getAddToQuestListUpdateNotify().add(subQuestWithCond);
}
}
} catch (Exception e) {
Grasscutter.getLogger().error("An error occurred while trying to fail quest.", e);
}
}
public void tryFinishSubQuests(QuestTrigger condType, String paramStr, int... params) {
try {
List<GameQuest> subQuestsWithCond =
getChildQuests().values().stream()
// There are subQuests with no acceptCond, but can be finished (example: 35104)
.filter(
p ->
p.getState() == QuestState.QUEST_STATE_UNFINISHED
&& p.getQuestData().getAcceptCond() != null)
.filter(
p ->
p.getQuestData().getFinishCond().stream()
.anyMatch(q -> q.getType() == condType))
.toList();
for (GameQuest subQuestWithCond : subQuestsWithCond) {
List<QuestData.QuestCondition> finishCond = subQuestWithCond.getQuestData().getFinishCond();
for (int i = 0; i < finishCond.size(); i++) {
QuestData.QuestCondition condition = finishCond.get(i);
if (condition.getType() == condType) {
boolean result =
this.getOwner()
.getServer()
.getQuestSystem()
.triggerContent(subQuestWithCond, condition, paramStr, params);
subQuestWithCond.getFinishProgressList()[i] = result ? 1 : 0;
if (result) {
getOwner().getSession().send(new PacketQuestProgressUpdateNotify(subQuestWithCond));
}
}
}
boolean shouldFinish =
LogicType.calculate(
subQuestWithCond.getQuestData().getFinishCondComb(),
subQuestWithCond.getFinishProgressList());
if (shouldFinish) {
subQuestWithCond.finish();
getQuestManager().getAddToQuestListUpdateNotify().add(subQuestWithCond);
}
}
} catch (Exception e) {
Grasscutter.getLogger().debug("An error occurred while trying to finish quest.", e);
}
}
public void save() {
DatabaseHelper.saveQuest(this);
}
public void delete() {
DatabaseHelper.deleteQuest(this);
}
public ParentQuest toProto() {
ParentQuest.Builder proto =
ParentQuest.newBuilder().setParentQuestId(getParentQuestId()).setIsFinished(isFinished());
proto
.setParentQuestState(getState().getValue())
.setVideoKey(QuestManager.getQuestKey(parentQuestId));
for (GameQuest quest : this.getChildQuests().values()) {
if (quest.getState() != QuestState.QUEST_STATE_UNSTARTED) {
ChildQuest childQuest =
ChildQuest.newBuilder()
.setQuestId(quest.getSubQuestId())
.setState(quest.getState().getValue())
.build();
proto.addChildQuestList(childQuest);
}
}
for (int i : getQuestVars()) {
proto.addQuestVar(i);
}
return proto.build();
}
}
package emu.grasscutter.game.quest;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.Transient;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.MainQuestData;
import emu.grasscutter.data.binout.MainQuestData.SubQuestData;
import emu.grasscutter.data.binout.MainQuestData.TalkData;
import emu.grasscutter.data.binout.ScriptSceneData;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.data.excels.RewardData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.quest.enums.*;
import emu.grasscutter.game.world.World;
import emu.grasscutter.net.proto.ChildQuestOuterClass.ChildQuest;
import emu.grasscutter.net.proto.ParentQuestOuterClass.ParentQuest;
import emu.grasscutter.server.packet.send.PacketCodexDataUpdateNotify;
import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify;
import emu.grasscutter.server.packet.send.PacketQuestProgressUpdateNotify;
import emu.grasscutter.utils.ConversionUtils;
import emu.grasscutter.utils.Position;
import lombok.Getter;
import lombok.val;
import org.bson.types.ObjectId;
import java.util.*;
@Entity(value = "quests", useDiscriminator = false)
public class GameMainQuest {
@Id private ObjectId id;
@Indexed @Getter private int ownerUid;
@Transient @Getter private Player owner;
@Transient @Getter private QuestManager questManager;
@Getter private Map<Integer, GameQuest> childQuests;
@Getter private int parentQuestId;
@Getter private int[] questVars;
@Getter private long[] timeVar;
//QuestUpdateQuestVarReq is sent in two stages...
@Getter private List<Integer> questVarsUpdate;
@Getter private ParentQuestState state;
@Getter private boolean isFinished;
@Getter List<QuestGroupSuite> questGroupSuites;
@Getter int[] suggestTrackMainQuestList;
@Getter private Map<Integer,TalkData> talks;
@Deprecated // Morphia only. Do not use.
public GameMainQuest() {}
public GameMainQuest(Player player, int parentQuestId) {
this.owner = player;
this.ownerUid = player.getUid();
this.questManager = player.getQuestManager();
this.parentQuestId = parentQuestId;
this.childQuests = new HashMap<>();
this.talks = new HashMap<>();
//official server always has a list of 5 questVars, with default value 0
this.questVars = new int[] {0,0,0,0,0};
this.timeVar = new long[] {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}; // theoretically max is 10 here
this.state = ParentQuestState.PARENT_QUEST_STATE_NONE;
this.questGroupSuites = new ArrayList<>();
addAllChildQuests();
}
private void addAllChildQuests() {
List<Integer> subQuestIds = Arrays.stream(GameData.getMainQuestDataMap().get(this.parentQuestId).getSubQuests()).map(SubQuestData::getSubId).toList();
for (Integer subQuestId : subQuestIds) {
QuestData questConfig = GameData.getQuestDataMap().get(subQuestId);
this.childQuests.put(subQuestId, new GameQuest(this, questConfig));
}
}
public Collection<GameQuest> getActiveQuests(){
return childQuests.values().stream()
.filter(q->q.getState().getValue() == QuestState.QUEST_STATE_UNFINISHED.getValue())
.toList();
}
public void setOwner(Player player) {
if (player.getUid() != this.getOwnerUid()) return;
this.owner = player;
}
public int getQuestVar(int i) {
return questVars[i];
}
public void setQuestVar(int i, int value) {
int previousValue = this.questVars[i];
this.questVars[i] = value;
Grasscutter.getLogger().debug("questVar {} value changed from {} to {}", i, previousValue, value);
}
public void incQuestVar(int i, int inc) {
int previousValue = this.questVars[i];
this.questVars[i] += inc;
Grasscutter.getLogger().debug("questVar {} value incremented from {} to {}", i, previousValue, previousValue + inc);
}
public void decQuestVar(int i, int dec) {
int previousValue = this.questVars[i];
this.questVars[i] -= dec;
Grasscutter.getLogger().debug("questVar {} value decremented from {} to {}", i, previousValue, previousValue - dec);
}
public GameQuest getChildQuestById(int id) {
return this.getChildQuests().get(id);
}
public GameQuest getChildQuestByOrder(int order) {
return this.getChildQuests().values().stream().filter(p -> p.getQuestData().getOrder() == order).toList().get(0);
}
public void finish() {
// Avoid recursion from child finish() in GameQuest
// when auto finishing all child quests with QUEST_STATE_UNFINISHED (below)
if (this.isFinished) {
Grasscutter.getLogger().debug("Skip main quest finishing because it's already finished");
return;
}
this.isFinished = true;
this.state = ParentQuestState.PARENT_QUEST_STATE_FINISHED;
/*
We also need to check for unfinished childQuests in this MainQuest
force them to complete and send a packet about this to the user,
because at some points there are special "invisible" child quests that control
some situations.
For example, subQuest 35312 is responsible for the event of leaving the territory
of the island with a statue and automatically returns the character back,
quest 35311 completes the main quest line 353 and starts 35501 from
new MainQuest 355 but if 35312 is not completed after the completion
of the main quest 353 - the character will not be able to leave place
(return again and again)
*/
this
.getChildQuests()
.values()
.stream()
.filter(p -> p.state != QuestState.QUEST_STATE_FINISHED)
.forEach(GameQuest::finish);
this.getOwner().getSession().send(new PacketFinishedParentQuestUpdateNotify(this));
this.getOwner().getSession().send(new PacketCodexDataUpdateNotify(this));
this.save();
// Add rewards
MainQuestData mainQuestData = GameData.getMainQuestDataMap().get(this.getParentQuestId());
if(mainQuestData.getRewardIdList()!=null) {
for (int rewardId : mainQuestData.getRewardIdList()) {
RewardData rewardData = GameData.getRewardDataMap().get(rewardId);
if (rewardData == null) {
continue;
}
getOwner().getInventory().addItemParamDatas(rewardData.getRewardItemList(), ActionReason.QuestReward);
}
}
// handoff main quest
if (mainQuestData.getSuggestTrackMainQuestList() != null) {
Arrays.stream(mainQuestData.getSuggestTrackMainQuestList())
.forEach(getQuestManager()::startMainQuest);
}
}
//TODO
public void fail() {}
public void cancel() {}
public List<Position> rewindTo(GameQuest targetQuest, boolean notifyDelete){
if(targetQuest == null || !targetQuest.rewind(notifyDelete)){
return null;
}
/*if(rewindPositions.isEmpty()){
addRewindPoints();
}*/
List<Position> posAndRot = new ArrayList<>();
if(hasRewindPosition(targetQuest.getSubQuestId(), posAndRot)){
return posAndRot;
}
List<GameQuest> rewindQuests = getChildQuests().values().stream()
.filter(p -> (p.getState() == QuestState.QUEST_STATE_UNFINISHED || p.getState() == QuestState.QUEST_STATE_FINISHED) &&
p.getQuestData() != null && p.getQuestData().isRewind()).toList();
for (GameQuest quest : rewindQuests) {
if (hasRewindPosition(quest.getSubQuestId(), posAndRot)) {
return posAndRot;
}
}
return null;
}
// Rewinds to the last finished/unfinished rewind quest, and returns the avatar rewind position (if it exists)
public List<Position> rewind() {
if (this.questManager == null) {
this.questManager = getOwner().getQuestManager();
}
var activeQuests = getActiveQuests();
var highestActiveQuest = activeQuests.stream()
.filter(q -> q.getQuestData() != null)
.max(Comparator.comparing(q -> q.getQuestData().getOrder()))
.orElse(null);
if (highestActiveQuest == null) {
var firstUnstarted = getChildQuests().values().stream()
.filter(q -> q.getQuestData() != null && q.getState().getValue() != QuestState.FINISHED.getValue())
.min(Comparator.comparingInt(a -> a.getQuestData().getOrder()));
if(firstUnstarted.isEmpty()){
// all quests are probably finished, do don't rewind and maybe also set the mainquest to finished?
return null;
}
highestActiveQuest = firstUnstarted.get();
//todo maybe try to accept quests if there is no active quest and no rewind target?
//tryAcceptSubQuests(QuestTrigger.QUEST_COND_NONE, "", 0);
}
var highestOrder = highestActiveQuest.getQuestData().getOrder();
var rewindTarget = getChildQuests().values().stream()
.filter(q -> q.getQuestData() != null)
.filter(q -> q.getQuestData().isRewind() && q.getQuestData().getOrder() <= highestOrder)
.max(Comparator.comparingInt(a -> a.getQuestData().getOrder()))
.orElse(null);
return rewindTo(rewindTarget!=null? rewindTarget : highestActiveQuest, false);
}
public boolean hasRewindPosition(int subId, List<Position> posAndRot){
RewindData questRewind = GameData.getRewindDataMap().get(subId);
if (questRewind == null) return false;
RewindData.AvatarData avatarData = questRewind.getAvatar();
if (avatarData == null) return false;
String avatarPos = avatarData.getPos();
QuestData.Guide guide = GameData.getQuestDataMap().get(subId).getGuide();
if (guide == null) return false;
int sceneId = guide.getGuideScene();
ScriptSceneData fullGlobals = GameData.getScriptSceneDataMap().get("flat.luas.scenes.full_globals.lua.json");
if (fullGlobals == null) return false;
ScriptSceneData.ScriptObject dummyPointScript = fullGlobals.getScriptObjectList().get(sceneId + "/scene" + sceneId + "_dummy_points.lua");
if (dummyPointScript == null) return false;
Map<String, List<Float>> dummyPointMap = dummyPointScript.getDummyPoints();
if (dummyPointMap == null) return false;
List<Float> avatarPosPos = dummyPointMap.get(avatarPos + ".pos");
List<Float> avatarPosRot = dummyPointMap.get(avatarPos + ".rot");
if (avatarPosPos == null) return false;
posAndRot.add(0, new Position(avatarPosPos.get(0),avatarPosPos.get(1),avatarPosPos.get(2))); // position
posAndRot.add(1, new Position(avatarPosRot.get(0),avatarPosRot.get(1),avatarPosRot.get(2))); //rotation
Grasscutter.getLogger().info("Succesfully loaded rewind data for subQuest {}", subId);
return true;
}
public boolean hasTeleportPostion(int subId, List<Position> posAndRot){
TeleportData questTransmit = GameData.getTeleportDataMap().get(subId);
if (questTransmit == null) return false;
TeleportData.TransmitPoint transmitPoint = questTransmit.getTransmit_points().size() > 0 ? questTransmit.getTransmit_points().get(0) : null;
if (transmitPoint == null) return false;
String transmitPos = transmitPoint.getPos();
int sceneId = transmitPoint.getScene_id();
ScriptSceneData fullGlobals = GameData.getScriptSceneDataMap().get("flat.luas.scenes.full_globals.lua.json");
if (fullGlobals == null) return false;
ScriptSceneData.ScriptObject dummyPointScript = fullGlobals.getScriptObjectList().get(sceneId + "/scene" + sceneId + "_dummy_points.lua");
if (dummyPointScript == null) return false;
Map<String, List<Float>> dummyPointMap = dummyPointScript.getDummyPoints();
if (dummyPointMap == null) return false;
List<Float> transmitPosPos = dummyPointMap.get(transmitPos + ".pos");
List<Float> transmitPosRot = dummyPointMap.get(transmitPos + ".rot");
if (transmitPosPos == null) return false;
posAndRot.add(0, new Position(transmitPosPos.get(0), transmitPosPos.get(1), transmitPosPos.get(2))); // position
posAndRot.add(1, new Position(transmitPosRot.get(0), transmitPosRot.get(1), transmitPosRot.get(2))); // rotation
Grasscutter.getLogger().info("Succesfully loaded teleport data for subQuest {}", subId);
return true;
}
public void checkProgress(){
for (var quest : getChildQuests().values()){
if(quest.getState() == QuestState.QUEST_STATE_UNFINISHED) {
questManager.checkQuestAlreadyFullfilled(quest);
}
}
}
public void tryAcceptSubQuests(QuestCond condType, String paramStr, int... params) {
try {
List<GameQuest> subQuestsWithCond = getChildQuests().values().stream()
.filter(p -> p.getState() == QuestState.QUEST_STATE_UNSTARTED || p.getState() == QuestState.UNFINISHED)
.filter(p -> p.getQuestData().getAcceptCond().stream().anyMatch(q -> condType == QuestCond.QUEST_COND_NONE || q.getType() == condType))
.toList();
var questSystem = owner.getServer().getQuestSystem();
for (GameQuest subQuestWithCond : subQuestsWithCond) {
var acceptCond = subQuestWithCond.getQuestData().getAcceptCond();
int[] accept = new int[acceptCond.size()];
for (int i = 0; i < subQuestWithCond.getQuestData().getAcceptCond().size(); i++) {
var condition = acceptCond.get(i);
boolean result = questSystem.triggerCondition(getOwner(), subQuestWithCond.getQuestData(), condition, paramStr, params);
accept[i] = result ? 1 : 0;
}
boolean shouldAccept = LogicType.calculate(subQuestWithCond.getQuestData().getAcceptCondComb(), accept);
if (shouldAccept)
subQuestWithCond.start();
}
this.save();
} catch (Exception e) {
Grasscutter.getLogger().error("An error occurred while trying to accept quest.", e);
}
}
public void tryFailSubQuests(QuestContent condType, String paramStr, int... params) {
try {
List<GameQuest> subQuestsWithCond = getChildQuests().values().stream()
.filter(p -> p.getState() == QuestState.QUEST_STATE_UNFINISHED)
.filter(p -> p.getQuestData().getFailCond().stream().anyMatch(q -> q.getType() == condType))
.toList();
for (GameQuest subQuestWithCond : subQuestsWithCond) {
val failCond = subQuestWithCond.getQuestData().getFailCond();
for (int i = 0; i < subQuestWithCond.getQuestData().getFailCond().size(); i++) {
val condition = failCond.get(i);
if (condition.getType() == condType) {
boolean result = this.getOwner().getServer().getQuestSystem().triggerContent(subQuestWithCond, condition, paramStr, params);
subQuestWithCond.getFailProgressList()[i] = result ? 1 : 0;
if (result) {
getOwner().getSession().send(new PacketQuestProgressUpdateNotify(subQuestWithCond));
}
}
}
boolean shouldFail = LogicType.calculate(subQuestWithCond.getQuestData().getFailCondComb(), subQuestWithCond.getFailProgressList());
if (shouldFail)
subQuestWithCond.fail();
}
} catch (Exception e) {
Grasscutter.getLogger().error("An error occurred while trying to fail quest.", e);
}
}
public void tryFinishSubQuests(QuestContent condType, String paramStr, int... params) {
try {
List<GameQuest> subQuestsWithCond = getChildQuests().values().stream()
//There are subQuests with no acceptCond, but can be finished (example: 35104)
.filter(p -> p.getState() == QuestState.QUEST_STATE_UNFINISHED && p.getQuestData().getAcceptCond() != null)
.filter(p -> p.getQuestData().getFinishCond().stream().anyMatch(q -> q.getType() == condType))
.toList();
for (GameQuest subQuestWithCond : subQuestsWithCond) {
val finishCond = subQuestWithCond.getQuestData().getFinishCond();
for (int i = 0; i < finishCond.size(); i++) {
val condition = finishCond.get(i);
if (condition.getType() == condType) {
boolean result = this.getOwner().getServer().getQuestSystem().triggerContent(subQuestWithCond, condition, paramStr, params);
subQuestWithCond.getFinishProgressList()[i] = result ? 1 : 0;
if (result) {
getOwner().getSession().send(new PacketQuestProgressUpdateNotify(subQuestWithCond));
}
}
}
boolean shouldFinish = LogicType.calculate(subQuestWithCond.getQuestData().getFinishCondComb(), subQuestWithCond.getFinishProgressList());
if (shouldFinish)
subQuestWithCond.finish();
}
} catch (Exception e) {
Grasscutter.getLogger().debug("An error occurred while trying to finish quest.", e);
}
}
public void save() {
DatabaseHelper.saveQuest(this);
}
public void delete() {
DatabaseHelper.deleteQuest(this);
}
public ParentQuest toProto(boolean withChildQuests) {
var proto = ParentQuest.newBuilder()
.setParentQuestId(getParentQuestId())
.setIsFinished(isFinished())
.setParentQuestState(getState().getValue())
.setVideoKey(QuestManager.getQuestKey(parentQuestId));
if (withChildQuests) {
for (var quest : this.getChildQuests().values()) {
if (quest.getState() != QuestState.QUEST_STATE_UNSTARTED) {
var childQuest = ChildQuest.newBuilder()
.setQuestId(quest.getSubQuestId())
.setState(quest.getState().getValue())
.build();
proto.addChildQuestList(childQuest);
}
}
}
for (int i : getQuestVars()) {
proto.addQuestVar(i);
}
return proto.build();
}
// TimeVar handling TODO check if ingame or irl time
public boolean initTimeVar(int index){
if(index >= this.timeVar.length){
Grasscutter.getLogger().error("Trying to init out of bounds time var {} for quest {}", index, this.parentQuestId);
return false;
}
this.timeVar[index] = owner.getWorld().getTotalGameTimeMinutes();
owner.getActiveQuestTimers().add(this.parentQuestId);
return true;
}
public boolean clearTimeVar(int index){
if(index >= this.timeVar.length){
Grasscutter.getLogger().error("Trying to clear out of bounds time var {} for quest {}", index, this.parentQuestId);
return false;
}
this.timeVar[index] = -1;
if(!checkActiveTimers()){
owner.getActiveQuestTimers().remove(this.parentQuestId);
}
return true;
}
public boolean checkActiveTimers(){
return Arrays.stream(timeVar).anyMatch(value -> value!=-1);
}
public long getDaysSinceTimeVar(int index){
if(index >= this.timeVar.length){
Grasscutter.getLogger().error("Trying to get days for out of bounds time var {} for quest {}", index, this.parentQuestId);
return -1;
}
val varTime = timeVar[index];
if(varTime == -1){
return 0;
}
return owner.getWorld().getTotalGameTimeDays() - ConversionUtils.gameTimeToDays(varTime);
}
public long getHoursSinceTimeVar(int index){
if(index >= this.timeVar.length){
Grasscutter.getLogger().error("Trying to get hours for out of bounds time var {} for quest {}", index, this.parentQuestId);
return -1;
}
val varTime = timeVar[index];
if(varTime == -1){
return 0;
}
return owner.getWorld().getTotalGameTimeDays() - ConversionUtils.gameTimeToDays(varTime);
}
}

View File

@@ -7,20 +7,25 @@ import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.ChapterData;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.data.excels.TriggerExcelConfigData;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.quest.enums.QuestCond;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.net.proto.ChapterStateOuterClass;
import emu.grasscutter.net.proto.QuestOuterClass.Quest;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.server.packet.send.PacketChapterStateNotify;
import emu.grasscutter.server.packet.send.PacketDelQuestNotify;
import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify;
import emu.grasscutter.utils.Utils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.script.Bindings;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
@Entity
public class GameQuest {
@@ -29,12 +34,14 @@ public class GameQuest {
@Getter private int subQuestId;
@Getter private int mainQuestId;
@Getter @Setter private QuestState state;
@Getter @Setter public QuestState state;
@Getter @Setter private int startTime;
@Getter @Setter private int acceptTime;
@Getter @Setter private int finishTime;
@Getter @Setter private long startGameDay;
@Getter private int[] finishProgressList;
@Getter private int[] failProgressList;
@Transient @Getter private Map<String, TriggerExcelConfigData> triggerData;
@@ -55,15 +62,17 @@ public class GameQuest {
}
public void start() {
clearProgress(false);
this.acceptTime = Utils.getCurrentSeconds();
this.startTime = this.acceptTime;
this.startGameDay = getOwner().getWorld().getTotalGameTimeDays();
this.state = QuestState.QUEST_STATE_UNFINISHED;
List<QuestData.QuestCondition> triggerCond =
val triggerCond =
questData.getFinishCond().stream()
.filter(p -> p.getType() == QuestTrigger.QUEST_CONTENT_TRIGGER_FIRE)
.filter(p -> p.getType() == QuestContent.QUEST_CONTENT_TRIGGER_FIRE)
.toList();
if (triggerCond.size() > 0) {
for (QuestData.QuestCondition cond : triggerCond) {
for (val cond : triggerCond) {
TriggerExcelConfigData newTrigger =
GameData.getTriggerExcelConfigDataMap().get(cond.getParam()[0]);
if (newTrigger != null) {
@@ -81,21 +90,10 @@ public class GameQuest {
}
}
if (questData.getFinishCond() != null && questData.getFinishCond().size() != 0) {
this.finishProgressList = new int[questData.getFinishCond().size()];
}
if (questData.getFailCond() != null && questData.getFailCond().size() != 0) {
this.failProgressList = new int[questData.getFailCond().size()];
}
getQuestData()
.getBeginExec()
.forEach(e -> getOwner().getServer().getQuestSystem().triggerExec(this, e, e.getParam()));
getOwner().sendPacket(new PacketQuestListUpdateNotify(this));
if (ChapterData.beginQuestChapterMap.containsKey(subQuestId)) {
mainQuest
.getOwner()
getOwner()
.sendPacket(
new PacketChapterStateNotify(
ChapterData.beginQuestChapterMap.get(subQuestId).getId(),
@@ -104,24 +102,24 @@ public class GameQuest {
// Some subQuests and talks become active when some other subQuests are unfinished (even from
// different MainQuests)
this.getOwner()
getOwner()
.getQuestManager()
.triggerEvent(
QuestTrigger.QUEST_CONTENT_QUEST_STATE_EQUAL,
this.getSubQuestId(),
this.getState().getValue(),
.queueEvent(
QuestContent.QUEST_CONTENT_QUEST_STATE_EQUAL,
getSubQuestId(),
getState().getValue(),
0,
0,
0);
this.getOwner()
getOwner()
.getQuestManager()
.triggerEvent(
QuestTrigger.QUEST_COND_STATE_EQUAL,
this.getSubQuestId(),
this.getState().getValue(),
0,
0,
0);
.queueEvent(
QuestCond.QUEST_COND_STATE_EQUAL, getSubQuestId(), getState().getValue(), 0, 0, 0);
getQuestData()
.getBeginExec()
.forEach(e -> getOwner().getServer().getQuestSystem().triggerExec(this, e, e.getParam()));
getOwner().getQuestManager().checkQuestAlreadyFullfilled(this);
Grasscutter.getLogger().debug("Quest {} is started", subQuestId);
}
@@ -141,7 +139,7 @@ public class GameQuest {
}
public void setConfig(QuestData config) {
if (getSubQuestId() != config.getId()) return;
if (config == null || getSubQuestId() != config.getId()) return;
this.questData = config;
}
@@ -153,11 +151,37 @@ public class GameQuest {
failProgressList[index] = value;
}
public boolean clearProgress(boolean notifyDelete) {
// TODO improve
var oldState = state;
if (questData.getFinishCond() != null && questData.getFinishCond().size() != 0) {
this.finishProgressList = new int[questData.getFinishCond().size()];
}
if (questData.getFailCond() != null && questData.getFailCond().size() != 0) {
this.failProgressList = new int[questData.getFailCond().size()];
}
setState(QuestState.QUEST_STATE_UNSTARTED);
finishTime = 0;
acceptTime = 0;
startTime = 0;
if (oldState == QuestState.QUEST_STATE_UNSTARTED) {
return false;
}
if (notifyDelete) {
getOwner().sendPacket(new PacketDelQuestNotify(getSubQuestId()));
}
save();
return true;
}
public void finish() {
this.state = QuestState.QUEST_STATE_FINISHED;
this.finishTime = Utils.getCurrentSeconds();
if (getQuestData().finishParent()) {
getOwner().sendPacket(new PacketQuestListUpdateNotify(this));
if (getQuestData().isFinishParent()) {
// This quest finishes the questline - the main quest will also save the quest to db, so we
// don't have to call save() here
getMainQuest().finish();
@@ -169,8 +193,8 @@ public class GameQuest {
// Some subQuests have conditions that subQuests are finished (even from different MainQuests)
getOwner()
.getQuestManager()
.triggerEvent(
QuestTrigger.QUEST_CONTENT_QUEST_STATE_EQUAL,
.queueEvent(
QuestContent.QUEST_CONTENT_QUEST_STATE_EQUAL,
this.subQuestId,
this.state.getValue(),
0,
@@ -178,8 +202,16 @@ public class GameQuest {
0);
getOwner()
.getQuestManager()
.triggerEvent(
QuestTrigger.QUEST_COND_STATE_EQUAL, this.subQuestId, this.state.getValue(), 0, 0, 0);
.queueEvent(QuestContent.QUEST_CONTENT_FINISH_PLOT, this.subQuestId, 0);
getOwner()
.getQuestManager()
.queueEvent(
QuestCond.QUEST_COND_STATE_EQUAL, this.subQuestId, this.state.getValue(), 0, 0, 0);
getOwner()
.getScene()
.triggerDungeonEvent(DungeonPassConditionType.DUNGEON_COND_FINISH_QUEST, getSubQuestId());
getOwner().getProgressManager().tryUnlockOpenStates();
if (ChapterData.endQuestChapterMap.containsKey(subQuestId)) {
mainQuest
@@ -190,6 +222,10 @@ public class GameQuest {
ChapterStateOuterClass.ChapterState.CHAPTER_STATE_END));
}
// hard coding to give amber
if (getQuestData().getSubId() == 35402) {
getOwner().getInventory().addItem(1021, 1, ActionReason.QuestItem); // amber item id
}
Grasscutter.getLogger().debug("Quest {} is finished", subQuestId);
}
@@ -198,43 +234,47 @@ public class GameQuest {
this.state = QuestState.QUEST_STATE_FAILED;
this.finishTime = Utils.getCurrentSeconds();
getQuestData()
.getFailExec()
.forEach(e -> getOwner().getServer().getQuestSystem().triggerExec(this, e, e.getParam()));
this.getOwner().sendPacket(new PacketQuestListUpdateNotify(this));
// Some subQuests have conditions that subQuests fail (even from different MainQuests)
getOwner()
this.getOwner()
.getQuestManager()
.triggerEvent(
QuestTrigger.QUEST_CONTENT_QUEST_STATE_EQUAL,
.queueEvent(
QuestContent.QUEST_CONTENT_QUEST_STATE_EQUAL,
this.subQuestId,
this.state.getValue(),
0,
0,
0);
getOwner()
this.getOwner()
.getQuestManager()
.triggerEvent(
QuestTrigger.QUEST_COND_STATE_EQUAL, this.subQuestId, this.state.getValue(), 0, 0, 0);
.queueEvent(
QuestCond.QUEST_COND_STATE_EQUAL, this.subQuestId, this.state.getValue(), 0, 0, 0);
this.getQuestData()
.getFailExec()
.forEach(e -> getOwner().getServer().getQuestSystem().triggerExec(this, e, e.getParam()));
if (this.getQuestData().getTrialAvatarList() != null) {
this.getQuestData()
.getTrialAvatarList()
.forEach(t -> this.getOwner().getTeamManager().removeTrialAvatar(t));
}
Grasscutter.getLogger().debug("Quest {} is failed", subQuestId);
}
// Return true if ParentQuest should rewind to this childQuest
public boolean rewind(GameQuest nextRewind) {
if (questData.isRewind()) {
if (nextRewind == null) {
return true;
}
// if the next isRewind subQuest is none or unstarted, reset all subQuests with order higher
// than this one, and restart this quest
if (nextRewind.getState() == QuestState.QUEST_STATE_NONE
|| nextRewind.getState() == QuestState.QUEST_STATE_UNSTARTED) {
getMainQuest().getChildQuests().values().stream()
.filter(p -> p.getQuestData().getOrder() > this.getQuestData().getOrder())
.forEach(q -> q.setState(QuestState.QUEST_STATE_UNSTARTED));
this.start();
return true;
}
}
return false;
// Return true if it did the rewind
public boolean rewind(boolean notifyDelete) {
getMainQuest().getChildQuests().values().stream()
.filter(p -> p.getQuestData().getOrder() > this.getQuestData().getOrder())
.forEach(
q -> {
q.clearProgress(notifyDelete);
});
clearProgress(notifyDelete);
this.start();
return true;
}
public void save() {

View File

@@ -1,12 +1,12 @@
package emu.grasscutter.game.quest;
import lombok.AccessLevel;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
public class QuestEncryptionKey {
int mainQuestId;
long encryptionKey;
}
package emu.grasscutter.game.quest;
import lombok.AccessLevel;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
public class QuestEncryptionKey {
int mainQuestId;
long encryptionKey;
}

View File

@@ -1,17 +1,17 @@
package emu.grasscutter.game.quest;
import dev.morphia.annotations.Entity;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@Builder(builderMethodName = "of")
@FieldDefaults(level = AccessLevel.PRIVATE)
public class QuestGroupSuite {
int scene;
int group;
int suite;
}
package emu.grasscutter.game.quest;
import dev.morphia.annotations.Entity;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity
@Data
@Builder(builderMethodName = "of")
@FieldDefaults(level = AccessLevel.PRIVATE)
public class QuestGroupSuite {
int scene;
int group;
int suite;
}

View File

@@ -8,34 +8,47 @@ import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.enums.ParentQuestState;
import emu.grasscutter.game.quest.enums.QuestCond;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.server.packet.send.PacketFinishedParentQuestUpdateNotify;
import emu.grasscutter.server.packet.send.PacketQuestListUpdateNotify;
import emu.grasscutter.utils.Position;
import io.netty.util.concurrent.FastThreadLocalThread;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
import lombok.val;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class QuestManager extends BasePlayerManager {
private static final Set<Integer> newPlayerMainQuests = Set.of(303, 318, 348, 349, 350, 351, 416, 500,
501, 502, 503, 504, 505, 506, 507, 508, 509, 20000, 20507, 20509, 21004, 21005, 21010, 21011, 21016, 21017,
21020, 21021, 21025, 40063, 70121, 70124, 70511, 71010, 71012, 71013, 71015, 71016, 71017, 71555);
@Getter
private final Player player;
@Getter
private final Int2ObjectMap<GameMainQuest> mainQuests;
@Getter private final Player player;
@Getter private final Int2ObjectMap<GameMainQuest> mainQuests;
private long lastHourCheck = 0;
private long lastDayCheck = 0;
public static final ExecutorService eventExecutor;
static {
eventExecutor = new ThreadPoolExecutor(4, 4,
60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1000),
FastThreadLocalThread::new, new ThreadPoolExecutor.AbortPolicy());
}
/*
On SetPlayerBornDataReq, the server sends FinishedParentQuestNotify, with this exact
parentQuestList. Captured on Game version 2.7
Note: quest 40063 is already set to finished, with childQuest 4006406's state set to 3
*/
@Getter
private final List<GameQuest> addToQuestListUpdateNotify;
private static Set<Integer> newPlayerMainQuests = Set.of(303,318,348,349,350,351,416,500,
501,502,503,504,505,506,507,508,509,20000,20507,20509,21004,21005,21010,21011,21016,21017,
21020,21021,21025,40063,70121,70124,70511,71010,71012,71013,71015,71016,71017,71555);
/*
On SetPlayerBornDataReq, the server sends ServerCondMeetQuestListUpdateNotify, with this exact
@@ -62,74 +75,119 @@ public class QuestManager extends BasePlayerManager {
*/
public static long getQuestKey(int mainQuestId) {
QuestEncryptionKey questEncryptionKey = GameData.getMainQuestEncryptionMap().get(mainQuestId);
return questEncryptionKey != null ? questEncryptionKey.getEncryptionKey() : 0L;
}
public QuestManager(Player player) {
super(player);
this.player = player;
this.mainQuests = new Int2ObjectOpenHashMap<>();
this.addToQuestListUpdateNotify = new ArrayList<>();
}
public static long getQuestKey(int mainQuestId) {
QuestEncryptionKey questEncryptionKey = GameData.getMainQuestEncryptionMap().get(mainQuestId);
return questEncryptionKey != null ? questEncryptionKey.getEncryptionKey() : 0L;
}
public void onNewPlayerCreate() {
public void onPlayerBorn() {
// TODO scan the quest and start the quest with acceptCond fulfilled
// The off send 3 request in that order: 1. FinishedParentQuestNotify, 2. QuestListNotify, 3. ServerCondMeetQuestListUpdateNotify
List<GameMainQuest> newQuests = this.addMultMainQuests(newPlayerMainQuests);
//getPlayer().sendPacket(new PacketServerCondMeetQuestListUpdateNotify(newPlayerServerCondMeetQuestListUpdateNotify));
for (GameMainQuest mainQuest : newQuests) {
startMainQuest(mainQuest.getParentQuestId());
}
//getPlayer().sendPacket(new PacketFinishedParentQuestUpdateNotify(newQuests));
//getPlayer().sendPacket(new PacketQuestListNotify(subQuests));
//getPlayer().sendPacket(new PacketServerCondMeetQuestListUpdateNotify(newPlayerServerCondMeetQuestListUpdateNotify));
}
public void onLogin() {
List<GameMainQuest> activeQuests = getActiveMainQuests();
List<GameQuest> activeSubs = new ArrayList<>(activeQuests.size());
for (GameMainQuest quest : activeQuests) {
List<Position> rewindPos = quest.rewind(); // <pos, rotation>
var activeQuest = quest.getActiveQuests();
if (rewindPos != null) {
getPlayer().getPosition().set(rewindPos.get(0));
getPlayer().getRotation().set(rewindPos.get(1));
}
if(activeQuest!=null && rewindPos!=null){
//activeSubs.add(activeQuest);
//player.sendPacket(new PacketQuestProgressUpdateNotify(activeQuest));
}
quest.checkProgress();
}
}
public void onTick(){
checkTimeVars();
// trigger game time tick for quests
queueEvent(QuestContent.QUEST_CONTENT_GAME_TIME_TICK,
player.getWorld().getGameTimeHours() , // hours
0);
}
private void checkTimeVars(){
val currentDays = player.getWorld().getTotalGameTimeDays();
val currentHours = player.getWorld().getTotalGameTimeHours();
boolean checkDays = currentDays != lastDayCheck;
boolean checkHours = currentHours != lastHourCheck;
if(!checkDays && !checkHours){
return;
}
this.lastDayCheck = currentDays;
this.lastHourCheck = currentHours;
player.getActiveQuestTimers().forEach(mainQuestId -> {
if(checkHours) {
queueEvent(QuestCond.QUEST_COND_TIME_VAR_GT_EQ, mainQuestId);
queueEvent(QuestContent.QUEST_CONTENT_TIME_VAR_GT_EQ, mainQuestId);
}
if(checkDays) {
queueEvent(QuestCond.QUEST_COND_TIME_VAR_PASS_DAY, mainQuestId);
queueEvent(QuestContent.QUEST_CONTENT_TIME_VAR_PASS_DAY, mainQuestId);
}
});
}
private List<GameMainQuest> addMultMainQuests(Set<Integer> mainQuestIds) {
List<GameMainQuest> newQuests = new ArrayList<>();
for (Integer id : mainQuestIds) {
getMainQuests().put(id.intValue(), new GameMainQuest(this.player, id));
getMainQuests().put(id.intValue(),new GameMainQuest(this.player, id));
getMainQuestById(id).save();
newQuests.add(getMainQuestById(id));
}
return newQuests;
}
public void enableQuests() {
onPlayerBorn();
}
/*
Looking through mainQuests 72201-72208 and 72174, we can infer that a questGlobalVar's default value is 0
*/
public Integer getQuestGlobalVarValue(Integer variable) {
return getPlayer().getQuestGlobalVariables().getOrDefault(variable, 0);
return getPlayer().getQuestGlobalVariables().getOrDefault(variable,0);
}
public void setQuestGlobalVarValue(Integer variable, Integer value) {
Integer previousValue = getPlayer().getQuestGlobalVariables().put(variable, value);
Grasscutter.getLogger().debug("Changed questGlobalVar {} value from {} to {}", variable, previousValue == null ? 0 : previousValue, value);
Integer previousValue = getPlayer().getQuestGlobalVariables().put(variable,value);
Grasscutter.getLogger().debug("Changed questGlobalVar {} value from {} to {}", variable, previousValue==null ? 0: previousValue, value);
}
public void incQuestGlobalVarValue(Integer variable, Integer inc) {
//
Integer previousValue = getPlayer().getQuestGlobalVariables().getOrDefault(variable, 0);
getPlayer().getQuestGlobalVariables().put(variable, previousValue + inc);
Integer previousValue = getPlayer().getQuestGlobalVariables().getOrDefault(variable,0);
getPlayer().getQuestGlobalVariables().put(variable,previousValue + inc);
Grasscutter.getLogger().debug("Incremented questGlobalVar {} value from {} to {}", variable, previousValue, previousValue + inc);
}
//In MainQuest 998, dec is passed as a positive integer
public void decQuestGlobalVarValue(Integer variable, Integer dec) {
//
Integer previousValue = getPlayer().getQuestGlobalVariables().getOrDefault(variable, 0);
getPlayer().getQuestGlobalVariables().put(variable, previousValue - dec);
Integer previousValue = getPlayer().getQuestGlobalVariables().getOrDefault(variable,0);
getPlayer().getQuestGlobalVariables().put(variable,previousValue - dec);
Grasscutter.getLogger().debug("Decremented questGlobalVar {} value from {} to {}", variable, previousValue, previousValue - dec);
}
@@ -137,6 +195,11 @@ public class QuestManager extends BasePlayerManager {
return getMainQuests().get(mainQuestId);
}
public GameMainQuest getMainQuestByTalkId(int talkId) {
int mainQuestId = GameData.getQuestTalkMap().getOrDefault(talkId, talkId / 100);
return getMainQuestById(mainQuestId);
}
public GameQuest getQuestById(int questId) {
QuestData questConfig = GameData.getQuestDataMap().get(questId);
if (questConfig == null) {
@@ -188,6 +251,7 @@ public class QuestManager extends BasePlayerManager {
public GameQuest addQuest(int questId) {
QuestData questConfig = GameData.getQuestDataMap().get(questId);
if (questConfig == null) {
return null;
}
@@ -205,14 +269,7 @@ public class QuestManager extends BasePlayerManager {
// Forcefully start
quest.start();
// Save main quest
mainQuest.save();
// Send packet
getPlayer().sendPacket(new PacketQuestListUpdateNotify(mainQuest.getChildQuests().values().stream()
.filter(p -> p.getState() != QuestState.QUEST_STATE_UNSTARTED)
.toList()));
checkQuestAlreadyFullfilled(quest);
return quest;
}
@@ -228,84 +285,79 @@ public class QuestManager extends BasePlayerManager {
.min(Comparator.comparingInt(MainQuestData.SubQuestData::getOrder))
.map(MainQuestData.SubQuestData::getSubId)
.ifPresent(this::addQuest);
//TODO find a better way then hardcoding to detect needed required quests
if(mainQuestId == 355){
startMainQuest(361);
startMainQuest(418);
startMainQuest(423);
startMainQuest(20509);
}
}
public void queueEvent(QuestCond condType, int... params) {
queueEvent(condType, "", params);
}
public void queueEvent(QuestContent condType, int... params) {
queueEvent(condType, "", params);
}
public void triggerEvent(QuestTrigger condType, int... params) {
triggerEvent(condType, "", params);
public void queueEvent(QuestContent condType, String paramStr, int... params) {
eventExecutor.submit(() -> triggerEvent(condType, paramStr, params));
}
public void queueEvent(QuestCond condType, String paramStr, int... params) {
eventExecutor.submit(() -> triggerEvent(condType, paramStr, params));
}
//TODO
public void triggerEvent(QuestTrigger condType, String paramStr, int... params) {
//QUEST_EXEC are handled directly by each subQuest
public void triggerEvent(QuestCond condType, String paramStr, int... params) {
Grasscutter.getLogger().debug("Trigger Event {}, {}, {}", condType, paramStr, params);
List<GameMainQuest> checkMainQuests = this.getMainQuests().values().stream()
.filter(i -> i.getState() != ParentQuestState.PARENT_QUEST_STATE_FINISHED)
.toList();
switch (condType) {
//accept Conds
case QUEST_COND_STATE_EQUAL:
case QUEST_COND_STATE_NOT_EQUAL:
case QUEST_COND_COMPLETE_TALK:
case QUEST_COND_LUA_NOTIFY:
case QUEST_COND_QUEST_VAR_EQUAL:
case QUEST_COND_QUEST_VAR_GREATER:
case QUEST_COND_QUEST_VAR_LESS:
case QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER:
case QUEST_COND_QUEST_GLOBAL_VAR_EQUAL:
case QUEST_COND_QUEST_GLOBAL_VAR_GREATER:
case QUEST_COND_QUEST_GLOBAL_VAR_LESS:
for (GameMainQuest mainquest : checkMainQuests) {
mainquest.tryAcceptSubQuests(condType, paramStr, params);
}
break;
//fail Conds
case QUEST_CONTENT_NOT_FINISH_PLOT:
for (GameMainQuest mainquest : checkMainQuests) {
mainquest.tryFailSubQuests(condType, paramStr, params);
}
break;
//finish Conds
case QUEST_CONTENT_COMPLETE_TALK:
case QUEST_CONTENT_FINISH_PLOT:
case QUEST_CONTENT_COMPLETE_ANY_TALK:
case QUEST_CONTENT_LUA_NOTIFY:
case QUEST_CONTENT_QUEST_VAR_EQUAL:
case QUEST_CONTENT_QUEST_VAR_GREATER:
case QUEST_CONTENT_QUEST_VAR_LESS:
case QUEST_CONTENT_ENTER_DUNGEON:
case QUEST_CONTENT_ENTER_ROOM:
case QUEST_CONTENT_INTERACT_GADGET:
case QUEST_CONTENT_TRIGGER_FIRE:
case QUEST_CONTENT_UNLOCK_TRANS_POINT:
case QUEST_CONTENT_SKILL:
for (GameMainQuest mainQuest : checkMainQuests) {
mainQuest.tryFinishSubQuests(condType, paramStr, params);
}
break;
//finish Or Fail Conds
case QUEST_CONTENT_GAME_TIME_TICK:
case QUEST_CONTENT_QUEST_STATE_EQUAL:
case QUEST_CONTENT_ADD_QUEST_PROGRESS:
case QUEST_CONTENT_LEAVE_SCENE:
for (GameMainQuest mainQuest : checkMainQuests) {
mainQuest.tryFailSubQuests(condType, paramStr, params);
mainQuest.tryFinishSubQuests(condType, paramStr, params);
}
break;
//QUEST_EXEC are handled directly by each subQuest
//Unused
case QUEST_CONTENT_QUEST_STATE_NOT_EQUAL:
case QUEST_COND_PLAYER_CHOOSE_MALE:
default:
Grasscutter.getLogger().error("Unhandled QuestTrigger {}", condType);
for (GameMainQuest mainquest : checkMainQuests) {
mainquest.tryAcceptSubQuests(condType, paramStr, params);
}
if (this.addToQuestListUpdateNotify.size() != 0) {
this.getPlayer().getSession().send(new PacketQuestListUpdateNotify(this.addToQuestListUpdateNotify));
this.addToQuestListUpdateNotify.clear();
}
public void triggerEvent(QuestContent condType, String paramStr, int... params) {
Grasscutter.getLogger().debug("Trigger Event {}, {}, {}", condType, paramStr, params);
List<GameMainQuest> checkMainQuests = this.getMainQuests().values().stream()
.filter(i -> i.getState() != ParentQuestState.PARENT_QUEST_STATE_FINISHED)
.toList();
for (GameMainQuest mainQuest : checkMainQuests) {
mainQuest.tryFailSubQuests(condType, paramStr, params);
mainQuest.tryFinishSubQuests(condType, paramStr, params);
}
}
/**
* TODO maybe trigger them delayed to allow basic communication finish first
* @param quest
*/
public void checkQuestAlreadyFullfilled(GameQuest quest){
Grasscutter.getGameServer().getScheduler().scheduleDelayedTask(() -> {
for(var condition : quest.getQuestData().getFinishCond()){
switch (condition.getType()) {
case QUEST_CONTENT_OBTAIN_ITEM, QUEST_CONTENT_ITEM_LESS_THAN -> {
//check if we already own enough of the item
var item = getPlayer().getInventory().getItemByGuid(condition.getParam()[0]);
queueEvent(condition.getType(), condition.getParam()[0], item != null ? item.getCount() : 0);
}
case QUEST_CONTENT_UNLOCK_TRANS_POINT -> {
var scenePoints = getPlayer().getUnlockedScenePoints().get(condition.getParam()[0]);
if (scenePoints != null && scenePoints.contains(condition.getParam()[1])) {
queueEvent(condition.getType(), condition.getParam()[0], condition.getParam()[1]);
}
}
case QUEST_CONTENT_UNLOCK_AREA -> {
var sceneAreas = getPlayer().getUnlockedSceneAreas().get(condition.getParam()[0]);
if (sceneAreas != null && sceneAreas.contains(condition.getParam()[1])) {
queueEvent(condition.getType(), condition.getParam()[0], condition.getParam()[1]);
}
}
}
}
}, 1);
}
public List<QuestGroupSuite> getSceneGroupSuite(int sceneId) {
@@ -317,7 +369,6 @@ public class QuestManager extends BasePlayerManager {
.filter(i -> i.getScene() == sceneId)
.toList();
}
public void loadFromDatabase() {
List<GameMainQuest> quests = DatabaseHelper.getAllQuests(getPlayer());

View File

@@ -1,9 +1,13 @@
package emu.grasscutter.game.quest;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.excels.QuestData.QuestCondition;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.data.excels.QuestData.QuestAcceptCondition;
import emu.grasscutter.data.excels.QuestData.QuestContentCondition;
import emu.grasscutter.data.excels.QuestData.QuestExecParam;
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.conditions.BaseCondition;
import emu.grasscutter.game.quest.content.BaseContent;
import emu.grasscutter.game.quest.handlers.QuestExecHandler;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
@@ -13,8 +17,8 @@ import org.reflections.Reflections;
@SuppressWarnings("unchecked")
public class QuestSystem extends BaseGameSystem {
private final Int2ObjectMap<QuestBaseHandler> condHandlers;
private final Int2ObjectMap<QuestBaseHandler> contHandlers;
private final Int2ObjectMap<BaseCondition> condHandlers;
private final Int2ObjectMap<BaseContent> contHandlers;
private final Int2ObjectMap<QuestExecHandler> execHandlers;
public QuestSystem(GameServer server) {
@@ -29,9 +33,9 @@ public class QuestSystem extends BaseGameSystem {
public void registerHandlers() {
this.registerHandlers(
this.condHandlers, "emu.grasscutter.game.quest.conditions", QuestBaseHandler.class);
this.condHandlers, "emu.grasscutter.game.quest.conditions", BaseCondition.class);
this.registerHandlers(
this.contHandlers, "emu.grasscutter.game.quest.content", QuestBaseHandler.class);
this.contHandlers, "emu.grasscutter.game.quest.content", BaseContent.class);
this.registerHandlers(
this.execHandlers, "emu.grasscutter.game.quest.exec", QuestExecHandler.class);
}
@@ -47,13 +51,25 @@ public class QuestSystem extends BaseGameSystem {
public <T> void registerPacketHandler(Int2ObjectMap<T> map, Class<? extends T> handlerClass) {
try {
QuestValue opcode = handlerClass.getAnnotation(QuestValue.class);
if (opcode == null || opcode.value().getValue() <= 0) {
int value = 0;
if (handlerClass.isAnnotationPresent(QuestValueExec.class)) {
QuestValueExec opcode = handlerClass.getAnnotation(QuestValueExec.class);
value = opcode.value().getValue();
} else if (handlerClass.isAnnotationPresent(QuestValueContent.class)) {
QuestValueContent opcode = handlerClass.getAnnotation(QuestValueContent.class);
value = opcode.value().getValue();
} else if (handlerClass.isAnnotationPresent(QuestValueCond.class)) {
QuestValueCond opcode = handlerClass.getAnnotation(QuestValueCond.class);
value = opcode.value().getValue();
} else {
return;
}
map.put(opcode.value().getValue(), handlerClass.getDeclaredConstructor().newInstance());
if (value <= 0) {
return;
}
map.put(value, handlerClass.getDeclaredConstructor().newInstance());
} catch (Exception e) {
e.printStackTrace();
}
@@ -62,24 +78,25 @@ public class QuestSystem extends BaseGameSystem {
// TODO make cleaner
public boolean triggerCondition(
GameQuest quest, QuestCondition condition, String paramStr, int... params) {
QuestBaseHandler handler = condHandlers.get(condition.getType().getValue());
Player owner,
QuestData questData,
QuestAcceptCondition condition,
String paramStr,
int... params) {
BaseCondition handler = condHandlers.get(condition.getType().getValue());
if (handler == null || quest.getQuestData() == null) {
if (handler == null || questData == null) {
Grasscutter.getLogger()
.debug(
"Could not trigger condition {} at {}",
condition.getType().getValue(),
quest.getQuestData());
.debug("Could not trigger condition {} at {}", condition.getType().getValue(), questData);
return false;
}
return handler.execute(quest, condition, paramStr, params);
return handler.execute(owner, questData, condition, paramStr, params);
}
public boolean triggerContent(
GameQuest quest, QuestCondition condition, String paramStr, int... params) {
QuestBaseHandler handler = contHandlers.get(condition.getType().getValue());
GameQuest quest, QuestContentCondition condition, String paramStr, int... params) {
BaseContent handler = contHandlers.get(condition.getType().getValue());
if (handler == null || quest.getQuestData() == null) {
Grasscutter.getLogger()
@@ -93,7 +110,7 @@ public class QuestSystem extends BaseGameSystem {
return handler.execute(quest, condition, paramStr, params);
}
public boolean triggerExec(GameQuest quest, QuestExecParam execParam, String... params) {
public void triggerExec(GameQuest quest, QuestExecParam execParam, String... params) {
QuestExecHandler handler = execHandlers.get(execParam.getType().getValue());
if (handler == null || quest.getQuestData() == null) {
@@ -102,9 +119,18 @@ public class QuestSystem extends BaseGameSystem {
"Could not trigger exec {} at {}",
execParam.getType().getValue(),
quest.getQuestData());
return false;
return;
}
return handler.execute(quest, execParam, params);
QuestManager.eventExecutor.submit(
() -> {
if (!handler.execute(quest, execParam, params)) {
Grasscutter.getLogger()
.debug(
"exec trigger failed {} at {}",
execParam.getType().getValue(),
quest.getQuestData());
}
});
}
}

View File

@@ -1,11 +1,10 @@
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();
}
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

@@ -1,11 +1,10 @@
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();
}
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

@@ -1,11 +1,10 @@
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();
}
package emu.grasscutter.game.quest;
import emu.grasscutter.game.quest.enums.QuestExec;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface QuestValueExec {
QuestExec value();
}

View File

@@ -15,7 +15,7 @@ public class RewindData {
}
@Data
private static class Npc {
public static class Npc {
private String script;
private int room_id;
private int data_index;

View File

@@ -1,35 +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;
}
}
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

@@ -1,18 +1,20 @@
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData.QuestCondition;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValue;
import emu.grasscutter.game.quest.enums.QuestTrigger;
import emu.grasscutter.game.quest.handlers.QuestBaseHandler;
import static emu.grasscutter.game.quest.enums.QuestCond.QUEST_COND_UNKNOWN;
@QuestValue(QuestTrigger.QUEST_CONTENT_NONE)
public class BaseCondition extends QuestBaseHandler {
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond;
@QuestValueCond(QUEST_COND_UNKNOWN)
public class BaseCondition {
@Override
public boolean execute(
GameQuest quest, QuestCondition condition, String paramStr, int... params) {
// TODO Auto-generated method stub
Player owner,
QuestData questData,
QuestData.QuestAcceptCondition condition,
String paramStr,
int... params) {
return false;
}
}

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