Fix player fields not being set

line seps r weird
This commit is contained in:
KingRainbow44
2023-04-10 22:46:19 -04:00
parent 5e56b5e3a8
commit 06cbae31fa
453 changed files with 63228 additions and 63071 deletions

View File

@@ -1,246 +1,246 @@
package emu.grasscutter.game.ability;
import com.google.protobuf.InvalidProtocolBufferException;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.data.binout.AbilityModifierEntry;
import emu.grasscutter.game.entity.EntityGadget;
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;
import emu.grasscutter.net.proto.AbilityMetaReInitOverrideMapOuterClass.AbilityMetaReInitOverrideMap;
import emu.grasscutter.net.proto.AbilityMixinCostStaminaOuterClass.AbilityMixinCostStamina;
import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalarValueEntry;
import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction;
import lombok.Getter;
public final class AbilityManager extends BasePlayerManager {
HealAbilityManager healAbilityManager;
@Getter private boolean abilityInvulnerable = false;
public AbilityManager(Player player) {
super(player);
this.healAbilityManager = new HealAbilityManager(player);
}
public void onAbilityInvoke(AbilityInvokeEntry invoke) throws Exception {
this.healAbilityManager.healHandler(invoke);
// Grasscutter.getLogger().info(invoke.getArgumentType() + " (" + invoke.getArgumentTypeValue()
// + "): " + Utils.bytesToHex(invoke.toByteArray()));
switch (invoke.getArgumentType()) {
case ABILITY_INVOKE_ARGUMENT_META_OVERRIDE_PARAM -> this.handleOverrideParam(invoke);
case ABILITY_INVOKE_ARGUMENT_META_REINIT_OVERRIDEMAP -> this.handleReinitOverrideMap(invoke);
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 -> {}
}
}
/**
* Invoked when a player starts a skill.
*
* @param player The player who started the skill.
* @param skillId The skill ID.
* @param casterId The caster ID.
*/
public void onSkillStart(Player player, int skillId, int casterId) {
// Check if the player matches this player.
if (player.getUid() != this.player.getUid()) {
return;
}
// Check if the caster matches the player.
if (player.getTeamManager().getCurrentAvatarEntity().getId() != casterId) {
return;
}
var skillData = GameData.getAvatarSkillDataMap().get(skillId);
if (skillData == null) {
return;
}
// Check if the skill is an elemental burst.
if (skillData.getCostElemVal() <= 0) {
return;
}
// Set the player as invulnerable.
this.abilityInvulnerable = true;
}
/**
* Invoked when a player ends a skill.
*
* @param player The player who started the skill.
*/
public void onSkillEnd(Player player) {
// Check if the player matches this player.
if (player.getUid() != this.player.getUid()) {
return;
}
// Check if the player is invulnerable.
if (!this.abilityInvulnerable) {
return;
}
// Set the player as not invulnerable.
this.abilityInvulnerable = false;
}
private void handleOverrideParam(AbilityInvokeEntry invoke) throws Exception {
GameEntity entity = this.player.getScene().getEntityById(invoke.getEntityId());
if (entity == null) {
return;
}
AbilityScalarValueEntry entry = AbilityScalarValueEntry.parseFrom(invoke.getAbilityData());
entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue());
}
private void handleReinitOverrideMap(AbilityInvokeEntry invoke) throws Exception {
GameEntity entity = this.player.getScene().getEntityById(invoke.getEntityId());
if (entity == null) {
return;
}
AbilityMetaReInitOverrideMap map =
AbilityMetaReInitOverrideMap.parseFrom(invoke.getAbilityData());
for (AbilityScalarValueEntry entry : map.getOverrideMapList()) {
entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue());
}
}
private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception {
// Sanity checks
GameEntity target = this.player.getScene().getEntityById(invoke.getEntityId());
if (target == null) {
return;
}
AbilityMetaModifierChange data = AbilityMetaModifierChange.parseFrom(invoke.getAbilityData());
if (data == null) {
return;
}
// Destroying rocks
if (target instanceof EntityGadget targetGadget
&& targetGadget.getContent() instanceof GadgetGatherObject gatherObject) {
if (data.getAction() == ModifierAction.MODIFIER_ACTION_REMOVED) {
gatherObject.dropItems(this.getPlayer());
return;
}
}
// Sanity checks
AbilityInvokeEntryHead head = invoke.getHead();
if (head == null) {
return;
}
GameEntity sourceEntity = this.player.getScene().getEntityById(data.getApplyEntityId());
if (sourceEntity == null) {
return;
}
// This is not how it works but we will keep it for now since healing abilities dont work
// properly anyways
if (data.getAction() == ModifierAction.MODIFIER_ACTION_ADDED
&& data.getParentAbilityName() != null) {
// Handle add modifier here
String modifierString = data.getParentAbilityName().getStr();
AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString);
if (modifier != null && modifier.getOnAdded().size() > 0) {
for (AbilityModifierAction action : modifier.getOnAdded()) {
this.invokeAction(action, target, sourceEntity);
}
}
// Add to meta modifier list
target.getMetaModifiers().put(head.getInstancedModifierId(), modifierString);
} else if (data.getAction() == ModifierAction.MODIFIER_ACTION_REMOVED) {
// Handle remove modifier
String modifierString = target.getMetaModifiers().get(head.getInstancedModifierId());
if (modifierString != null) {
// Get modifier and call on remove event
AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString);
if (modifier != null && modifier.getOnRemoved().size() > 0) {
for (AbilityModifierAction action : modifier.getOnRemoved()) {
this.invokeAction(action, target, sourceEntity);
}
}
// Remove from meta modifiers
target.getMetaModifiers().remove(head.getInstancedModifierId());
}
}
}
private void handleMixinCostStamina(AbilityInvokeEntry invoke)
throws InvalidProtocolBufferException {
AbilityMixinCostStamina costStamina =
AbilityMixinCostStamina.parseFrom((invoke.getAbilityData()));
this.getPlayer().getStaminaManager().handleMixinCostStamina(costStamina.getIsSwim());
}
private void handleGenerateElemBall(AbilityInvokeEntry invoke)
throws InvalidProtocolBufferException {
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) {
case HealHP -> {}
case LoseHP -> {
if (action.amountByTargetCurrentHPRatio == null) {
return;
}
float damageAmount = action.amount.get();
// if (action.amount.isDynamic && action.amount.dynamicKey != null) {
// damageAmount =
// sourceEntity.getMetaOverrideMap().getOrDefault(action.amount.dynamicKey, 0f);
// }
if (damageAmount > 0) {
target.damage(damageAmount);
}
}
default -> {}
}
}
}
package emu.grasscutter.game.ability;
import com.google.protobuf.InvalidProtocolBufferException;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.data.binout.AbilityModifierEntry;
import emu.grasscutter.game.entity.EntityGadget;
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;
import emu.grasscutter.net.proto.AbilityMetaReInitOverrideMapOuterClass.AbilityMetaReInitOverrideMap;
import emu.grasscutter.net.proto.AbilityMixinCostStaminaOuterClass.AbilityMixinCostStamina;
import emu.grasscutter.net.proto.AbilityScalarValueEntryOuterClass.AbilityScalarValueEntry;
import emu.grasscutter.net.proto.ModifierActionOuterClass.ModifierAction;
import lombok.Getter;
public final class AbilityManager extends BasePlayerManager {
HealAbilityManager healAbilityManager;
@Getter private boolean abilityInvulnerable = false;
public AbilityManager(Player player) {
super(player);
this.healAbilityManager = new HealAbilityManager(player);
}
public void onAbilityInvoke(AbilityInvokeEntry invoke) throws Exception {
this.healAbilityManager.healHandler(invoke);
// Grasscutter.getLogger().info(invoke.getArgumentType() + " (" + invoke.getArgumentTypeValue()
// + "): " + Utils.bytesToHex(invoke.toByteArray()));
switch (invoke.getArgumentType()) {
case ABILITY_INVOKE_ARGUMENT_META_OVERRIDE_PARAM -> this.handleOverrideParam(invoke);
case ABILITY_INVOKE_ARGUMENT_META_REINIT_OVERRIDEMAP -> this.handleReinitOverrideMap(invoke);
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 -> {}
}
}
/**
* Invoked when a player starts a skill.
*
* @param player The player who started the skill.
* @param skillId The skill ID.
* @param casterId The caster ID.
*/
public void onSkillStart(Player player, int skillId, int casterId) {
// Check if the player matches this player.
if (player.getUid() != this.player.getUid()) {
return;
}
// Check if the caster matches the player.
if (player.getTeamManager().getCurrentAvatarEntity().getId() != casterId) {
return;
}
var skillData = GameData.getAvatarSkillDataMap().get(skillId);
if (skillData == null) {
return;
}
// Check if the skill is an elemental burst.
if (skillData.getCostElemVal() <= 0) {
return;
}
// Set the player as invulnerable.
this.abilityInvulnerable = true;
}
/**
* Invoked when a player ends a skill.
*
* @param player The player who started the skill.
*/
public void onSkillEnd(Player player) {
// Check if the player matches this player.
if (player.getUid() != this.player.getUid()) {
return;
}
// Check if the player is invulnerable.
if (!this.abilityInvulnerable) {
return;
}
// Set the player as not invulnerable.
this.abilityInvulnerable = false;
}
private void handleOverrideParam(AbilityInvokeEntry invoke) throws Exception {
GameEntity entity = this.player.getScene().getEntityById(invoke.getEntityId());
if (entity == null) {
return;
}
AbilityScalarValueEntry entry = AbilityScalarValueEntry.parseFrom(invoke.getAbilityData());
entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue());
}
private void handleReinitOverrideMap(AbilityInvokeEntry invoke) throws Exception {
GameEntity entity = this.player.getScene().getEntityById(invoke.getEntityId());
if (entity == null) {
return;
}
AbilityMetaReInitOverrideMap map =
AbilityMetaReInitOverrideMap.parseFrom(invoke.getAbilityData());
for (AbilityScalarValueEntry entry : map.getOverrideMapList()) {
entity.getMetaOverrideMap().put(entry.getKey().getStr(), entry.getFloatValue());
}
}
private void handleModifierChange(AbilityInvokeEntry invoke) throws Exception {
// Sanity checks
GameEntity target = this.player.getScene().getEntityById(invoke.getEntityId());
if (target == null) {
return;
}
AbilityMetaModifierChange data = AbilityMetaModifierChange.parseFrom(invoke.getAbilityData());
if (data == null) {
return;
}
// Destroying rocks
if (target instanceof EntityGadget targetGadget
&& targetGadget.getContent() instanceof GadgetGatherObject gatherObject) {
if (data.getAction() == ModifierAction.MODIFIER_ACTION_REMOVED) {
gatherObject.dropItems(this.getPlayer());
return;
}
}
// Sanity checks
AbilityInvokeEntryHead head = invoke.getHead();
if (head == null) {
return;
}
GameEntity sourceEntity = this.player.getScene().getEntityById(data.getApplyEntityId());
if (sourceEntity == null) {
return;
}
// This is not how it works but we will keep it for now since healing abilities dont work
// properly anyways
if (data.getAction() == ModifierAction.MODIFIER_ACTION_ADDED
&& data.getParentAbilityName() != null) {
// Handle add modifier here
String modifierString = data.getParentAbilityName().getStr();
AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString);
if (modifier != null && modifier.getOnAdded().size() > 0) {
for (AbilityModifierAction action : modifier.getOnAdded()) {
this.invokeAction(action, target, sourceEntity);
}
}
// Add to meta modifier list
target.getMetaModifiers().put(head.getInstancedModifierId(), modifierString);
} else if (data.getAction() == ModifierAction.MODIFIER_ACTION_REMOVED) {
// Handle remove modifier
String modifierString = target.getMetaModifiers().get(head.getInstancedModifierId());
if (modifierString != null) {
// Get modifier and call on remove event
AbilityModifierEntry modifier = GameData.getAbilityModifiers().get(modifierString);
if (modifier != null && modifier.getOnRemoved().size() > 0) {
for (AbilityModifierAction action : modifier.getOnRemoved()) {
this.invokeAction(action, target, sourceEntity);
}
}
// Remove from meta modifiers
target.getMetaModifiers().remove(head.getInstancedModifierId());
}
}
}
private void handleMixinCostStamina(AbilityInvokeEntry invoke)
throws InvalidProtocolBufferException {
AbilityMixinCostStamina costStamina =
AbilityMixinCostStamina.parseFrom((invoke.getAbilityData()));
this.getPlayer().getStaminaManager().handleMixinCostStamina(costStamina.getIsSwim());
}
private void handleGenerateElemBall(AbilityInvokeEntry invoke)
throws InvalidProtocolBufferException {
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) {
case HealHP -> {}
case LoseHP -> {
if (action.amountByTargetCurrentHPRatio == null) {
return;
}
float damageAmount = action.amount.get();
// if (action.amount.isDynamic && action.amount.dynamicKey != null) {
// damageAmount =
// sourceEntity.getMetaOverrideMap().getOrDefault(action.amount.dynamicKey, 0f);
// }
if (damageAmount > 0) {
target.damage(damageAmount);
}
}
default -> {}
}
}
}

View File

@@ -1,323 +1,323 @@
package emu.grasscutter.game.achievement;
import com.github.davidmoten.guavamini.Lists;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Transient;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.achievement.AchievementData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.StatusOuterClass;
import emu.grasscutter.server.packet.send.PacketAchievementAllDataNotify;
import emu.grasscutter.server.packet.send.PacketAchievementUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeAchievementGoalRewardRsp;
import emu.grasscutter.server.packet.send.PacketTakeAchievementRewardRsp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntSupplier;
import javax.annotation.Nullable;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import org.bson.types.ObjectId;
@Entity("achievements")
@Data
@Builder(builderMethodName = "of")
public class Achievements {
private static final IntSupplier currentTimeSecs =
() -> (int) (System.currentTimeMillis() / 1000L);
private static final Achievement INVALID =
new Achievement(StatusOuterClass.Status.STATUS_INVALID, -1, 0, 0, 0);
@Id private ObjectId id;
private int uid;
@Transient private Player player;
private Map<Integer, Achievement> achievementList;
@Getter private int finishedAchievementNum;
private List<Integer> takenGoalRewardIdList;
public static Achievements getByPlayer(Player player) {
var achievements =
player.getAchievements() == null
? DatabaseHelper.getAchievementData(player.getUid())
: player.getAchievements();
if (achievements == null) {
achievements = create(player.getUid());
}
return achievements;
}
public static Achievements create(int uid) {
var newAchievement =
Achievements.of()
.uid(uid)
.achievementList(init())
.finishedAchievementNum(0)
.takenGoalRewardIdList(Lists.newArrayList())
.build();
newAchievement.save();
return newAchievement;
}
private static Map<Integer, Achievement> init() {
Map<Integer, Achievement> map = new HashMap<>();
GameData.getAchievementDataMap().values().stream()
.filter(AchievementData::isUsed)
.forEach(
a -> {
map.put(
a.getId(),
new Achievement(
StatusOuterClass.Status.STATUS_UNFINISHED, a.getId(), a.getProgress(), 0, 0));
});
return map;
}
public AchievementControlReturns grant(int achievementId) {
var a = this.getAchievement(achievementId);
if (a == null || this.isFinished(achievementId)) {
return a == null
? AchievementControlReturns.achievementNotFound()
: AchievementControlReturns.alreadyAchieved();
}
return this.progress(achievementId, a.getTotalProgress());
}
public AchievementControlReturns revoke(int achievementId) {
var a = this.getAchievement(achievementId);
if (a == null || !this.isFinished(achievementId)) {
return a == null
? AchievementControlReturns.achievementNotFound()
: AchievementControlReturns.notYetAchieved();
}
return this.progress(achievementId, 0);
}
public AchievementControlReturns progress(int achievementId, int progress) {
var a = this.getAchievement(achievementId);
if (a == null) {
return AchievementControlReturns.achievementNotFound();
}
a.setCurProgress(progress);
return AchievementControlReturns.success(this.notifyOtherAchievements(a));
}
private int notifyOtherAchievements(Achievement a) {
var changedNum = new AtomicInteger();
changedNum.addAndGet(this.update(a) ? 1 : 0);
GameData.getAchievementDataMap().get(a.getId()).getExcludedGroupAchievementIdList().stream()
.map(this::getAchievement)
.filter(Objects::nonNull)
.forEach(
other -> {
other.setCurProgress(a.getCurProgress());
changedNum.addAndGet(this.update(other) ? 1 : 0);
});
this.computeFinishedAchievementNum();
this.save();
this.sendUpdatePacket(a);
return changedNum.intValue();
}
private boolean update(Achievement a) {
if (a.getStatus() == StatusOuterClass.Status.STATUS_UNFINISHED
&& a.getCurProgress() >= a.getTotalProgress()) {
a.setStatus(StatusOuterClass.Status.STATUS_FINISHED);
a.setFinishTimestampSec(currentTimeSecs.getAsInt());
return true;
} else if (this.isFinished(a.getId()) && a.getCurProgress() < a.getTotalProgress()) {
a.setStatus(StatusOuterClass.Status.STATUS_UNFINISHED);
a.setFinishTimestampSec(0);
return true;
}
return false;
}
private void computeFinishedAchievementNum() {
this.finishedAchievementNum =
GameData.getAchievementDataMap().values().stream()
.filter(a -> this.isFinished(a.getId()))
.mapToInt(value -> 1)
.sum();
}
private void sendUpdatePacket(Achievement achievement) {
List<Achievement> achievements = Lists.newArrayList(achievement);
achievements.addAll(
GameData.getAchievementDataMap()
.get(achievement.getId())
.getExcludedGroupAchievementIdList()
.stream()
.map(this::getAchievement)
.filter(Objects::nonNull)
.toList());
this.sendUpdatePacket(achievements);
}
private void sendUpdatePacket(List<Achievement> achievement) {
if (this.isPacketSendable()) {
this.player.sendPacket(new PacketAchievementUpdateNotify(achievement));
}
}
@Nullable public Achievement getAchievement(int achievementId) {
if (this.isInvalid(achievementId)) {
return null;
}
return this.getAchievementList()
.computeIfAbsent(
achievementId,
id -> {
return new Achievement(
StatusOuterClass.Status.STATUS_UNFINISHED,
id,
GameData.getAchievementDataMap().get(id.intValue()).getProgress(),
0,
0);
});
}
public boolean isInvalid(int achievementId) {
var data = GameData.getAchievementDataMap().get(achievementId);
return data == null || data.isDisuse();
}
public StatusOuterClass.Status getStatus(int achievementId) {
return this.getAchievementList().getOrDefault(achievementId, INVALID).getStatus();
}
public boolean isFinished(int achievementId) {
var status = this.getStatus(achievementId);
return status == StatusOuterClass.Status.STATUS_FINISHED
|| status == StatusOuterClass.Status.STATUS_REWARD_TAKEN;
}
public void takeReward(List<Integer> ids) {
List<GameItem> rewards = Lists.newArrayList();
for (int i : ids) {
var target = GameData.getAchievementDataMap().get(i);
if (target == null) {
Grasscutter.getLogger().warn("null returned while taking reward!");
return;
}
if (this.isRewardTaken(i)) {
this.player.sendPacket(new PacketTakeAchievementRewardRsp());
return;
}
var data = GameData.getRewardDataMap().get(target.getFinishRewardId());
if (data == null) {
Grasscutter.getLogger().warn("null returned while getting reward data!");
continue;
}
data.getRewardItemList()
.forEach(
itemParamData -> {
var itemData = GameData.getItemDataMap().get(itemParamData.getId());
if (itemData == null) {
Grasscutter.getLogger().warn("itemData == null!");
return;
}
rewards.add(new GameItem(itemData, itemParamData.getCount()));
});
var a = this.getAchievement(i);
a.setStatus(StatusOuterClass.Status.STATUS_REWARD_TAKEN);
this.save();
this.sendUpdatePacket(a);
}
this.player.getInventory().addItems(rewards, ActionReason.AchievementReward);
this.player.sendPacket(
new PacketTakeAchievementRewardRsp(
ids, rewards.stream().map(GameItem::toItemParam).toList()));
}
public void takeGoalReward(List<Integer> ids) {
List<GameItem> rewards = Lists.newArrayList();
for (int i : ids) {
if (this.takenGoalRewardIdList.contains(i)) {
this.player.sendPacket(new PacketTakeAchievementGoalRewardRsp());
}
var goalData = GameData.getAchievementGoalDataMap().get(i);
if (goalData == null) {
Grasscutter.getLogger().warn("null returned while getting goal reward data!");
continue;
}
var data = GameData.getRewardDataMap().get(goalData.getFinishRewardId());
if (data == null) {
Grasscutter.getLogger().warn("null returned while getting reward data!");
continue;
}
data.getRewardItemList()
.forEach(
itemParamData -> {
var itemData = GameData.getItemDataMap().get(itemParamData.getId());
if (itemData == null) {
Grasscutter.getLogger().warn("itemData == null!");
return;
}
rewards.add(new GameItem(itemData, itemParamData.getCount()));
});
this.takenGoalRewardIdList.add(i);
this.save();
}
this.player.getInventory().addItems(rewards, ActionReason.AchievementGoalReward);
this.player.sendPacket(
new PacketTakeAchievementGoalRewardRsp(
ids, rewards.stream().map(GameItem::toItemParam).toList()));
}
public boolean isRewardTaken(int achievementId) {
return this.getStatus(achievementId) == StatusOuterClass.Status.STATUS_REWARD_TAKEN;
}
public boolean isRewardLeft(int achievementId) {
return this.getStatus(achievementId) == StatusOuterClass.Status.STATUS_FINISHED;
}
private boolean isPacketSendable() {
return this.player != null;
}
public void save() {
DatabaseHelper.saveAchievementData(this);
}
public void onLogin(Player player) {
if (this.player == null) {
this.player = player;
}
this.player.sendPacket(new PacketAchievementAllDataNotify(this.player));
}
}
package emu.grasscutter.game.achievement;
import com.github.davidmoten.guavamini.Lists;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Transient;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.achievement.AchievementData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.StatusOuterClass;
import emu.grasscutter.server.packet.send.PacketAchievementAllDataNotify;
import emu.grasscutter.server.packet.send.PacketAchievementUpdateNotify;
import emu.grasscutter.server.packet.send.PacketTakeAchievementGoalRewardRsp;
import emu.grasscutter.server.packet.send.PacketTakeAchievementRewardRsp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.IntSupplier;
import javax.annotation.Nullable;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import org.bson.types.ObjectId;
@Entity("achievements")
@Data
@Builder(builderMethodName = "of")
public class Achievements {
private static final IntSupplier currentTimeSecs =
() -> (int) (System.currentTimeMillis() / 1000L);
private static final Achievement INVALID =
new Achievement(StatusOuterClass.Status.STATUS_INVALID, -1, 0, 0, 0);
@Id private ObjectId id;
private int uid;
@Transient private Player player;
private Map<Integer, Achievement> achievementList;
@Getter private int finishedAchievementNum;
private List<Integer> takenGoalRewardIdList;
public static Achievements getByPlayer(Player player) {
var achievements =
player.getAchievements() == null
? DatabaseHelper.getAchievementData(player.getUid())
: player.getAchievements();
if (achievements == null) {
achievements = create(player.getUid());
}
return achievements;
}
public static Achievements create(int uid) {
var newAchievement =
Achievements.of()
.uid(uid)
.achievementList(init())
.finishedAchievementNum(0)
.takenGoalRewardIdList(Lists.newArrayList())
.build();
newAchievement.save();
return newAchievement;
}
private static Map<Integer, Achievement> init() {
Map<Integer, Achievement> map = new HashMap<>();
GameData.getAchievementDataMap().values().stream()
.filter(AchievementData::isUsed)
.forEach(
a -> {
map.put(
a.getId(),
new Achievement(
StatusOuterClass.Status.STATUS_UNFINISHED, a.getId(), a.getProgress(), 0, 0));
});
return map;
}
public AchievementControlReturns grant(int achievementId) {
var a = this.getAchievement(achievementId);
if (a == null || this.isFinished(achievementId)) {
return a == null
? AchievementControlReturns.achievementNotFound()
: AchievementControlReturns.alreadyAchieved();
}
return this.progress(achievementId, a.getTotalProgress());
}
public AchievementControlReturns revoke(int achievementId) {
var a = this.getAchievement(achievementId);
if (a == null || !this.isFinished(achievementId)) {
return a == null
? AchievementControlReturns.achievementNotFound()
: AchievementControlReturns.notYetAchieved();
}
return this.progress(achievementId, 0);
}
public AchievementControlReturns progress(int achievementId, int progress) {
var a = this.getAchievement(achievementId);
if (a == null) {
return AchievementControlReturns.achievementNotFound();
}
a.setCurProgress(progress);
return AchievementControlReturns.success(this.notifyOtherAchievements(a));
}
private int notifyOtherAchievements(Achievement a) {
var changedNum = new AtomicInteger();
changedNum.addAndGet(this.update(a) ? 1 : 0);
GameData.getAchievementDataMap().get(a.getId()).getExcludedGroupAchievementIdList().stream()
.map(this::getAchievement)
.filter(Objects::nonNull)
.forEach(
other -> {
other.setCurProgress(a.getCurProgress());
changedNum.addAndGet(this.update(other) ? 1 : 0);
});
this.computeFinishedAchievementNum();
this.save();
this.sendUpdatePacket(a);
return changedNum.intValue();
}
private boolean update(Achievement a) {
if (a.getStatus() == StatusOuterClass.Status.STATUS_UNFINISHED
&& a.getCurProgress() >= a.getTotalProgress()) {
a.setStatus(StatusOuterClass.Status.STATUS_FINISHED);
a.setFinishTimestampSec(currentTimeSecs.getAsInt());
return true;
} else if (this.isFinished(a.getId()) && a.getCurProgress() < a.getTotalProgress()) {
a.setStatus(StatusOuterClass.Status.STATUS_UNFINISHED);
a.setFinishTimestampSec(0);
return true;
}
return false;
}
private void computeFinishedAchievementNum() {
this.finishedAchievementNum =
GameData.getAchievementDataMap().values().stream()
.filter(a -> this.isFinished(a.getId()))
.mapToInt(value -> 1)
.sum();
}
private void sendUpdatePacket(Achievement achievement) {
List<Achievement> achievements = Lists.newArrayList(achievement);
achievements.addAll(
GameData.getAchievementDataMap()
.get(achievement.getId())
.getExcludedGroupAchievementIdList()
.stream()
.map(this::getAchievement)
.filter(Objects::nonNull)
.toList());
this.sendUpdatePacket(achievements);
}
private void sendUpdatePacket(List<Achievement> achievement) {
if (this.isPacketSendable()) {
this.player.sendPacket(new PacketAchievementUpdateNotify(achievement));
}
}
@Nullable public Achievement getAchievement(int achievementId) {
if (this.isInvalid(achievementId)) {
return null;
}
return this.getAchievementList()
.computeIfAbsent(
achievementId,
id -> {
return new Achievement(
StatusOuterClass.Status.STATUS_UNFINISHED,
id,
GameData.getAchievementDataMap().get(id.intValue()).getProgress(),
0,
0);
});
}
public boolean isInvalid(int achievementId) {
var data = GameData.getAchievementDataMap().get(achievementId);
return data == null || data.isDisuse();
}
public StatusOuterClass.Status getStatus(int achievementId) {
return this.getAchievementList().getOrDefault(achievementId, INVALID).getStatus();
}
public boolean isFinished(int achievementId) {
var status = this.getStatus(achievementId);
return status == StatusOuterClass.Status.STATUS_FINISHED
|| status == StatusOuterClass.Status.STATUS_REWARD_TAKEN;
}
public void takeReward(List<Integer> ids) {
List<GameItem> rewards = Lists.newArrayList();
for (int i : ids) {
var target = GameData.getAchievementDataMap().get(i);
if (target == null) {
Grasscutter.getLogger().warn("null returned while taking reward!");
return;
}
if (this.isRewardTaken(i)) {
this.player.sendPacket(new PacketTakeAchievementRewardRsp());
return;
}
var data = GameData.getRewardDataMap().get(target.getFinishRewardId());
if (data == null) {
Grasscutter.getLogger().warn("null returned while getting reward data!");
continue;
}
data.getRewardItemList()
.forEach(
itemParamData -> {
var itemData = GameData.getItemDataMap().get(itemParamData.getId());
if (itemData == null) {
Grasscutter.getLogger().warn("itemData == null!");
return;
}
rewards.add(new GameItem(itemData, itemParamData.getCount()));
});
var a = this.getAchievement(i);
a.setStatus(StatusOuterClass.Status.STATUS_REWARD_TAKEN);
this.save();
this.sendUpdatePacket(a);
}
this.player.getInventory().addItems(rewards, ActionReason.AchievementReward);
this.player.sendPacket(
new PacketTakeAchievementRewardRsp(
ids, rewards.stream().map(GameItem::toItemParam).toList()));
}
public void takeGoalReward(List<Integer> ids) {
List<GameItem> rewards = Lists.newArrayList();
for (int i : ids) {
if (this.takenGoalRewardIdList.contains(i)) {
this.player.sendPacket(new PacketTakeAchievementGoalRewardRsp());
}
var goalData = GameData.getAchievementGoalDataMap().get(i);
if (goalData == null) {
Grasscutter.getLogger().warn("null returned while getting goal reward data!");
continue;
}
var data = GameData.getRewardDataMap().get(goalData.getFinishRewardId());
if (data == null) {
Grasscutter.getLogger().warn("null returned while getting reward data!");
continue;
}
data.getRewardItemList()
.forEach(
itemParamData -> {
var itemData = GameData.getItemDataMap().get(itemParamData.getId());
if (itemData == null) {
Grasscutter.getLogger().warn("itemData == null!");
return;
}
rewards.add(new GameItem(itemData, itemParamData.getCount()));
});
this.takenGoalRewardIdList.add(i);
this.save();
}
this.player.getInventory().addItems(rewards, ActionReason.AchievementGoalReward);
this.player.sendPacket(
new PacketTakeAchievementGoalRewardRsp(
ids, rewards.stream().map(GameItem::toItemParam).toList()));
}
public boolean isRewardTaken(int achievementId) {
return this.getStatus(achievementId) == StatusOuterClass.Status.STATUS_REWARD_TAKEN;
}
public boolean isRewardLeft(int achievementId) {
return this.getStatus(achievementId) == StatusOuterClass.Status.STATUS_FINISHED;
}
private boolean isPacketSendable() {
return this.player != null;
}
public void save() {
DatabaseHelper.saveAchievementData(this);
}
public void onLogin(Player player) {
if (this.player == null) {
this.player = player;
}
this.player.sendPacket(new PacketAchievementAllDataNotify(this.player));
}
}

View File

@@ -1,32 +1,32 @@
package emu.grasscutter.game.activity;
import java.util.Date;
import java.util.List;
import lombok.AccessLevel;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ActivityConfigItem {
int activityId;
int activityType;
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;
}
}
}
package emu.grasscutter.game.activity;
import java.util.Date;
import java.util.List;
import lombok.AccessLevel;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ActivityConfigItem {
int activityId;
int activityType;
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

@@ -1,139 +1,139 @@
package emu.grasscutter.game.activity;
import com.esotericsoftware.reflectasm.ConstructorAccess;
import emu.grasscutter.data.GameData;
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.*;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
public abstract class ActivityHandler {
/** Must set before initWatchers */
@Getter ActivityConfigItem activityConfigItem;
@Getter ActivityData activityData;
Map<WatcherTriggerType, List<ActivityWatcher>> watchersMap = new HashMap<>();
public abstract void onProtoBuild(
PlayerActivityData playerActivityData,
ActivityInfoOuterClass.ActivityInfo.Builder activityInfo);
public abstract void onInitPlayerActivityData(PlayerActivityData playerActivityData);
public void initWatchers(Map<WatcherTriggerType, ConstructorAccess<?>> activityWatcherTypeMap) {
activityData = GameData.getActivityDataMap().get(activityConfigItem.getActivityId());
// add watcher to map by id
activityData
.getWatcherDataList()
.forEach(
watcherData -> {
var watcherType =
activityWatcherTypeMap.get(
watcherData.getTriggerConfig().getWatcherTriggerType());
ActivityWatcher watcher;
if (watcherType != null) {
watcher = (ActivityWatcher) watcherType.newInstance();
} else {
watcher = new DefaultWatcher();
}
watcher.setWatcherId(watcherData.getId());
watcher.setActivityHandler(this);
watcher.setActivityWatcherData(watcherData);
watchersMap.computeIfAbsent(
watcherData.getTriggerConfig().getWatcherTriggerType(), k -> new ArrayList<>());
watchersMap.get(watcherData.getTriggerConfig().getWatcherTriggerType()).add(watcher);
});
}
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)
.map(PlayerActivityData.WatcherInfo::init)
.collect(Collectors.toMap(PlayerActivityData.WatcherInfo::getWatcherId, y -> y));
}
public PlayerActivityData initPlayerActivityData(Player player) {
PlayerActivityData playerActivityData =
PlayerActivityData.of()
.activityId(activityConfigItem.getActivityId())
.uid(player.getUid())
.watcherInfoMap(initWatchersDataForPlayer())
.build();
onInitPlayerActivityData(playerActivityData);
return playerActivityData;
}
public ActivityInfoOuterClass.ActivityInfo toProto(
PlayerActivityData playerActivityData, ActivityConditionExecutor conditionExecutor) {
var proto = ActivityInfoOuterClass.ActivityInfo.newBuilder();
proto
.setActivityId(activityConfigItem.getActivityId())
.setActivityType(activityConfigItem.getActivityType())
.setScheduleId(activityConfigItem.getScheduleId())
.setBeginTime(DateHelper.getUnixTime(activityConfigItem.getBeginTime()))
.setFirstDayStartTime(DateHelper.getUnixTime(activityConfigItem.getBeginTime()))
.setEndTime(DateHelper.getUnixTime(activityConfigItem.getEndTime()))
.addAllMeetCondList(getMeetConditions(conditionExecutor));
if (playerActivityData != null) {
proto.addAllWatcherInfoList(playerActivityData.getAllWatcherInfoList());
}
onProtoBuild(playerActivityData, proto);
return proto.build();
}
}
package emu.grasscutter.game.activity;
import com.esotericsoftware.reflectasm.ConstructorAccess;
import emu.grasscutter.data.GameData;
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.*;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
public abstract class ActivityHandler {
/** Must set before initWatchers */
@Getter ActivityConfigItem activityConfigItem;
@Getter ActivityData activityData;
Map<WatcherTriggerType, List<ActivityWatcher>> watchersMap = new HashMap<>();
public abstract void onProtoBuild(
PlayerActivityData playerActivityData,
ActivityInfoOuterClass.ActivityInfo.Builder activityInfo);
public abstract void onInitPlayerActivityData(PlayerActivityData playerActivityData);
public void initWatchers(Map<WatcherTriggerType, ConstructorAccess<?>> activityWatcherTypeMap) {
activityData = GameData.getActivityDataMap().get(activityConfigItem.getActivityId());
// add watcher to map by id
activityData
.getWatcherDataList()
.forEach(
watcherData -> {
var watcherType =
activityWatcherTypeMap.get(
watcherData.getTriggerConfig().getWatcherTriggerType());
ActivityWatcher watcher;
if (watcherType != null) {
watcher = (ActivityWatcher) watcherType.newInstance();
} else {
watcher = new DefaultWatcher();
}
watcher.setWatcherId(watcherData.getId());
watcher.setActivityHandler(this);
watcher.setActivityWatcherData(watcherData);
watchersMap.computeIfAbsent(
watcherData.getTriggerConfig().getWatcherTriggerType(), k -> new ArrayList<>());
watchersMap.get(watcherData.getTriggerConfig().getWatcherTriggerType()).add(watcher);
});
}
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)
.map(PlayerActivityData.WatcherInfo::init)
.collect(Collectors.toMap(PlayerActivityData.WatcherInfo::getWatcherId, y -> y));
}
public PlayerActivityData initPlayerActivityData(Player player) {
PlayerActivityData playerActivityData =
PlayerActivityData.of()
.activityId(activityConfigItem.getActivityId())
.uid(player.getUid())
.watcherInfoMap(initWatchersDataForPlayer())
.build();
onInitPlayerActivityData(playerActivityData);
return playerActivityData;
}
public ActivityInfoOuterClass.ActivityInfo toProto(
PlayerActivityData playerActivityData, ActivityConditionExecutor conditionExecutor) {
var proto = ActivityInfoOuterClass.ActivityInfo.newBuilder();
proto
.setActivityId(activityConfigItem.getActivityId())
.setActivityType(activityConfigItem.getActivityType())
.setScheduleId(activityConfigItem.getScheduleId())
.setBeginTime(DateHelper.getUnixTime(activityConfigItem.getBeginTime()))
.setFirstDayStartTime(DateHelper.getUnixTime(activityConfigItem.getBeginTime()))
.setEndTime(DateHelper.getUnixTime(activityConfigItem.getEndTime()))
.addAllMeetCondList(getMeetConditions(conditionExecutor));
if (playerActivityData != null) {
proto.addAllWatcherInfoList(playerActivityData.getAllWatcherInfoList());
}
onProtoBuild(playerActivityData, proto);
return proto.build();
}
}

View File

@@ -1,231 +1,231 @@
package emu.grasscutter.game.activity;
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;
import emu.grasscutter.game.props.WatcherTriggerType;
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;
@Getter
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<>();
scheduleActivityConfigMap = new HashMap<>();
loadActivityConfigData();
}
private static void loadActivityConfigData() {
// scan activity type handler & watcher type
var activityHandlerTypeMap = new HashMap<ActivityType, ConstructorAccess<?>>();
var activityWatcherTypeMap = new HashMap<WatcherTriggerType, ConstructorAccess<?>>();
var reflections = new Reflections(ActivityManager.class.getPackage().getName());
reflections
.getSubTypesOf(ActivityHandler.class)
.forEach(
item -> {
var typeName = item.getAnnotation(GameActivity.class);
activityHandlerTypeMap.put(typeName.value(), ConstructorAccess.get(item));
});
reflections
.getSubTypesOf(ActivityWatcher.class)
.forEach(
item -> {
var typeName = item.getAnnotation(ActivityWatcherType.class);
activityWatcherTypeMap.put(typeName.value(), ConstructorAccess.get(item));
});
try {
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());
return;
}
var activityHandlerType =
activityHandlerTypeMap.get(
ActivityType.getTypeByName(activityData.getActivityType()));
ActivityHandler activityHandler;
if (activityHandlerType != null) {
activityHandler = (ActivityHandler) activityHandlerType.newInstance();
} else {
activityHandler = new DefaultActivityHandler();
}
activityHandler.setActivityConfigItem(item);
activityHandler.initWatchers(activityWatcherTypeMap);
item.setActivityHandler(activityHandler);
activityConfigItemMap.putIfAbsent(item.getActivityId(), item);
scheduleActivityConfigMap.putIfAbsent(item.getScheduleId(), item);
});
Grasscutter.getLogger().info("Enable {} activities.", activityConfigItemMap.size());
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load activities config.", e);
}
}
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 =
activityConfigItemMap.values().stream()
.map(ActivityConfigItem::getActivityHandler)
.filter(Objects::nonNull)
.map(ActivityHandler::getWatchersMap)
.map(map -> map.get(watcherTriggerType))
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.toList();
watchers.forEach(
watcher ->
watcher.trigger(
playerActivityDataMap.get(
watcher.getActivityHandler().getActivityConfigItem().getActivityId()),
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, conditionExecutor);
}
public Optional<ActivityHandler> getActivityHandler(ActivityType type) {
return activityConfigItemMap.values().stream()
.map(ActivityConfigItem::getActivityHandler)
.filter(x -> type == x.getClass().getAnnotation(GameActivity.class).value())
.findFirst();
}
public <T extends ActivityHandler> Optional<T> getActivityHandlerAs(
ActivityType type, Class<T> clazz) {
return getActivityHandler(type).map(x -> (T) x);
}
public Optional<Integer> getActivityIdByActivityType(ActivityType type) {
return getActivityHandler(type)
.map(ActivityHandler::getActivityConfigItem)
.map(ActivityConfigItem::getActivityId);
}
public Optional<PlayerActivityData> getPlayerActivityDataByActivityType(ActivityType type) {
return getActivityIdByActivityType(type).map(playerActivityDataMap::get);
}
public Optional<ActivityInfoOuterClass.ActivityInfo> getInfoProtoByActivityType(
ActivityType type) {
return getActivityIdByActivityType(type).map(this::getInfoProtoByActivityId);
}
}
package emu.grasscutter.game.activity;
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;
import emu.grasscutter.game.props.WatcherTriggerType;
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;
@Getter
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<>();
scheduleActivityConfigMap = new HashMap<>();
loadActivityConfigData();
}
private static void loadActivityConfigData() {
// scan activity type handler & watcher type
var activityHandlerTypeMap = new HashMap<ActivityType, ConstructorAccess<?>>();
var activityWatcherTypeMap = new HashMap<WatcherTriggerType, ConstructorAccess<?>>();
var reflections = new Reflections(ActivityManager.class.getPackage().getName());
reflections
.getSubTypesOf(ActivityHandler.class)
.forEach(
item -> {
var typeName = item.getAnnotation(GameActivity.class);
activityHandlerTypeMap.put(typeName.value(), ConstructorAccess.get(item));
});
reflections
.getSubTypesOf(ActivityWatcher.class)
.forEach(
item -> {
var typeName = item.getAnnotation(ActivityWatcherType.class);
activityWatcherTypeMap.put(typeName.value(), ConstructorAccess.get(item));
});
try {
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());
return;
}
var activityHandlerType =
activityHandlerTypeMap.get(
ActivityType.getTypeByName(activityData.getActivityType()));
ActivityHandler activityHandler;
if (activityHandlerType != null) {
activityHandler = (ActivityHandler) activityHandlerType.newInstance();
} else {
activityHandler = new DefaultActivityHandler();
}
activityHandler.setActivityConfigItem(item);
activityHandler.initWatchers(activityWatcherTypeMap);
item.setActivityHandler(activityHandler);
activityConfigItemMap.putIfAbsent(item.getActivityId(), item);
scheduleActivityConfigMap.putIfAbsent(item.getScheduleId(), item);
});
Grasscutter.getLogger().info("Enable {} activities.", activityConfigItemMap.size());
} catch (Exception e) {
Grasscutter.getLogger().error("Unable to load activities config.", e);
}
}
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 =
activityConfigItemMap.values().stream()
.map(ActivityConfigItem::getActivityHandler)
.filter(Objects::nonNull)
.map(ActivityHandler::getWatchersMap)
.map(map -> map.get(watcherTriggerType))
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.toList();
watchers.forEach(
watcher ->
watcher.trigger(
playerActivityDataMap.get(
watcher.getActivityHandler().getActivityConfigItem().getActivityId()),
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, conditionExecutor);
}
public Optional<ActivityHandler> getActivityHandler(ActivityType type) {
return activityConfigItemMap.values().stream()
.map(ActivityConfigItem::getActivityHandler)
.filter(x -> type == x.getClass().getAnnotation(GameActivity.class).value())
.findFirst();
}
public <T extends ActivityHandler> Optional<T> getActivityHandlerAs(
ActivityType type, Class<T> clazz) {
return getActivityHandler(type).map(x -> (T) x);
}
public Optional<Integer> getActivityIdByActivityType(ActivityType type) {
return getActivityHandler(type)
.map(ActivityHandler::getActivityConfigItem)
.map(ActivityConfigItem::getActivityId);
}
public Optional<PlayerActivityData> getPlayerActivityDataByActivityType(ActivityType type) {
return getActivityIdByActivityType(type).map(playerActivityDataMap::get);
}
public Optional<ActivityInfoOuterClass.ActivityInfo> getInfoProtoByActivityType(
ActivityType type) {
return getActivityIdByActivityType(type).map(this::getInfoProtoByActivityId);
}
}

View File

@@ -1,25 +1,25 @@
package emu.grasscutter.game.activity;
import emu.grasscutter.data.excels.activity.ActivityWatcherData;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
public abstract class ActivityWatcher {
int watcherId;
ActivityWatcherData activityWatcherData;
ActivityHandler activityHandler;
protected abstract boolean isMeet(String... param);
public void trigger(PlayerActivityData playerActivityData, String... param) {
if (isMeet(param)) {
playerActivityData.addWatcherProgress(watcherId);
playerActivityData.save();
}
}
}
package emu.grasscutter.game.activity;
import emu.grasscutter.data.excels.activity.ActivityWatcherData;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
public abstract class ActivityWatcher {
int watcherId;
ActivityWatcherData activityWatcherData;
ActivityHandler activityHandler;
protected abstract boolean isMeet(String... param);
public void trigger(PlayerActivityData playerActivityData, String... param) {
if (isMeet(param)) {
playerActivityData.addWatcherProgress(watcherId);
playerActivityData.save();
}
}
}

View File

@@ -1,134 +1,134 @@
package emu.grasscutter.game.activity;
import dev.morphia.annotations.Entity;
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.activity.ActivityWatcherData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.ActivityWatcherInfoOuterClass;
import emu.grasscutter.server.packet.send.PacketActivityUpdateWatcherNotify;
import emu.grasscutter.utils.JsonUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity("activities")
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class PlayerActivityData {
@Id String id;
int uid;
int activityId;
Map<Integer, WatcherInfo> watcherInfoMap;
/** the detail data of each type of activity (Json format) */
String detail;
@Transient Player player;
@Transient ActivityHandler activityHandler;
public static PlayerActivityData getByPlayer(Player player, int activityId) {
return DatabaseHelper.getPlayerActivityData(player.getUid(), activityId);
}
public void save() {
DatabaseHelper.savePlayerActivityData(this);
}
public synchronized void addWatcherProgress(int watcherId) {
var watcherInfo = watcherInfoMap.get(watcherId);
if (watcherInfo == null) {
return;
}
if (watcherInfo.curProgress >= watcherInfo.totalProgress) {
return;
}
watcherInfo.curProgress++;
getPlayer().sendPacket(new PacketActivityUpdateWatcherNotify(activityId, watcherInfo));
}
public List<ActivityWatcherInfoOuterClass.ActivityWatcherInfo> getAllWatcherInfoList() {
return watcherInfoMap.values().stream().map(WatcherInfo::toProto).toList();
}
public void setDetail(Object detail) {
this.detail = JsonUtils.encode(detail);
}
public void takeWatcherReward(int watcherId) {
var watcher = watcherInfoMap.get(watcherId);
if (watcher == null || watcher.isTakenReward()) {
return;
}
var reward =
Optional.of(watcher)
.map(WatcherInfo::getMetadata)
.map(ActivityWatcherData::getRewardID)
.map(id -> GameData.getRewardDataMap().get(id.intValue()));
if (reward.isEmpty()) {
return;
}
List<GameItem> rewards = new ArrayList<>();
for (ItemParamData param : reward.get().getRewardItemList()) {
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
}
player.getInventory().addItems(rewards, ActionReason.ActivityWatcher);
watcher.setTakenReward(true);
save();
}
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class WatcherInfo {
int watcherId;
int totalProgress;
int curProgress;
boolean isTakenReward;
/**
* @return True when the progress of this watcher has reached the total progress.
*/
public boolean isFinished() {
return this.curProgress >= this.totalProgress;
}
public static WatcherInfo init(ActivityWatcher watcher) {
return WatcherInfo.of()
.watcherId(watcher.getWatcherId())
.totalProgress(watcher.getActivityWatcherData().getProgress())
.isTakenReward(false)
.build();
}
public ActivityWatcherData getMetadata() {
return GameData.getActivityWatcherDataMap().get(watcherId);
}
public ActivityWatcherInfoOuterClass.ActivityWatcherInfo toProto() {
return ActivityWatcherInfoOuterClass.ActivityWatcherInfo.newBuilder()
.setWatcherId(watcherId)
.setCurProgress(curProgress)
.setTotalProgress(totalProgress)
.setIsTakenReward(isTakenReward)
.build();
}
}
}
package emu.grasscutter.game.activity;
import dev.morphia.annotations.Entity;
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.activity.ActivityWatcherData;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.net.proto.ActivityWatcherInfoOuterClass;
import emu.grasscutter.server.packet.send.PacketActivityUpdateWatcherNotify;
import emu.grasscutter.utils.JsonUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.FieldDefaults;
@Entity("activities")
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public class PlayerActivityData {
@Id String id;
int uid;
int activityId;
Map<Integer, WatcherInfo> watcherInfoMap;
/** the detail data of each type of activity (Json format) */
String detail;
@Transient Player player;
@Transient ActivityHandler activityHandler;
public static PlayerActivityData getByPlayer(Player player, int activityId) {
return DatabaseHelper.getPlayerActivityData(player.getUid(), activityId);
}
public void save() {
DatabaseHelper.savePlayerActivityData(this);
}
public synchronized void addWatcherProgress(int watcherId) {
var watcherInfo = watcherInfoMap.get(watcherId);
if (watcherInfo == null) {
return;
}
if (watcherInfo.curProgress >= watcherInfo.totalProgress) {
return;
}
watcherInfo.curProgress++;
getPlayer().sendPacket(new PacketActivityUpdateWatcherNotify(activityId, watcherInfo));
}
public List<ActivityWatcherInfoOuterClass.ActivityWatcherInfo> getAllWatcherInfoList() {
return watcherInfoMap.values().stream().map(WatcherInfo::toProto).toList();
}
public void setDetail(Object detail) {
this.detail = JsonUtils.encode(detail);
}
public void takeWatcherReward(int watcherId) {
var watcher = watcherInfoMap.get(watcherId);
if (watcher == null || watcher.isTakenReward()) {
return;
}
var reward =
Optional.of(watcher)
.map(WatcherInfo::getMetadata)
.map(ActivityWatcherData::getRewardID)
.map(id -> GameData.getRewardDataMap().get(id.intValue()));
if (reward.isEmpty()) {
return;
}
List<GameItem> rewards = new ArrayList<>();
for (ItemParamData param : reward.get().getRewardItemList()) {
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
}
player.getInventory().addItems(rewards, ActionReason.ActivityWatcher);
watcher.setTakenReward(true);
save();
}
@Entity
@Data
@FieldDefaults(level = AccessLevel.PRIVATE)
@Builder(builderMethodName = "of")
public static class WatcherInfo {
int watcherId;
int totalProgress;
int curProgress;
boolean isTakenReward;
/**
* @return True when the progress of this watcher has reached the total progress.
*/
public boolean isFinished() {
return this.curProgress >= this.totalProgress;
}
public static WatcherInfo init(ActivityWatcher watcher) {
return WatcherInfo.of()
.watcherId(watcher.getWatcherId())
.totalProgress(watcher.getActivityWatcherData().getProgress())
.isTakenReward(false)
.build();
}
public ActivityWatcherData getMetadata() {
return GameData.getActivityWatcherDataMap().get(watcherId);
}
public ActivityWatcherInfoOuterClass.ActivityWatcherInfo toProto() {
return ActivityWatcherInfoOuterClass.ActivityWatcherInfo.newBuilder()
.setWatcherId(watcherId)
.setCurProgress(curProgress)
.setTotalProgress(totalProgress)
.setIsTakenReward(isTakenReward)
.build();
}
}
}

View File

@@ -1,16 +1,16 @@
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();
}
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,23 +1,23 @@
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);
}
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,63 +1,63 @@
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;
}
}
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,75 +1,75 @@
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);
}
}
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,81 +1,81 @@
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;
}
}
}
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,17 +1,17 @@
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
}
}
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 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];
}
}
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,25 +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,21 +1,21 @@
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
}
}
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,18 +1,18 @@
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];
}
}
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,21 +1,21 @@
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;
}
}
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,20 +1,20 @@
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;
}
}
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,17 +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,35 +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 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);
}
}
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,164 +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.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())));
}
}
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 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();
}
}
}
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();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,173 +1,173 @@
package emu.grasscutter.game.avatar;
import emu.grasscutter.data.GameData;
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;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.PacketAvatarChangeCostumeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarFlycloakChangeNotify;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.util.Iterator;
import java.util.List;
public class AvatarStorage extends BasePlayerManager implements Iterable<Avatar> {
private final Int2ObjectMap<Avatar> avatars;
private final Long2ObjectMap<Avatar> avatarsGuid;
public AvatarStorage(Player player) {
super(player);
this.avatars = new Int2ObjectOpenHashMap<>();
this.avatarsGuid = new Long2ObjectOpenHashMap<>();
}
public Int2ObjectMap<Avatar> getAvatars() {
return avatars;
}
public int getAvatarCount() {
return this.avatars.size();
}
public Avatar getAvatarById(int id) {
return getAvatars().get(id);
}
public Avatar getAvatarByGuid(long id) {
return avatarsGuid.get(id);
}
public boolean hasAvatar(int id) {
return getAvatars().containsKey(id);
}
public boolean addAvatar(Avatar avatar) {
if (avatar.getAvatarData() == null || this.hasAvatar(avatar.getAvatarId())) {
return false;
}
// Set owner first
avatar.setOwner(getPlayer());
// Put into maps
this.avatars.put(avatar.getAvatarId(), avatar);
this.avatarsGuid.put(avatar.getGuid(), avatar);
avatar.save();
return true;
}
public void addStartingWeapon(Avatar avatar) {
// Make sure avatar owner is this player
if (avatar.getPlayer() != this.getPlayer()) {
return;
}
// Create weapon
GameItem weapon = new GameItem(avatar.getAvatarData().getInitialWeapon());
if (weapon.getItemData() != null) {
this.getPlayer().getInventory().addItem(weapon);
avatar.equipItem(weapon, true);
}
}
public boolean wearFlycloak(long avatarGuid, int flycloakId) {
Avatar avatar = this.getAvatarByGuid(avatarGuid);
if (avatar == null || !getPlayer().getFlyCloakList().contains(flycloakId)) {
return false;
}
avatar.setFlyCloak(flycloakId);
avatar.save();
// Update
getPlayer().sendPacket(new PacketAvatarFlycloakChangeNotify(avatar));
return true;
}
public boolean changeCostume(long avatarGuid, int costumeId) {
Avatar avatar = this.getAvatarByGuid(avatarGuid);
if (avatar == null) {
return false;
}
if (costumeId != 0 && !getPlayer().getCostumeList().contains(costumeId)) {
return false;
}
// TODO make sure avatar can wear costume
avatar.setCostume(costumeId);
avatar.save();
// Update entity
EntityAvatar entity = avatar.getAsEntity();
if (entity == null) {
entity = new EntityAvatar(avatar);
getPlayer().sendPacket(new PacketAvatarChangeCostumeNotify(entity));
} else {
getPlayer().getScene().broadcastPacket(new PacketAvatarChangeCostumeNotify(entity));
}
// Done
return true;
}
public void loadFromDatabase() {
List<Avatar> avatars = DatabaseHelper.getAvatars(getPlayer());
for (Avatar avatar : avatars) {
// Should never happen
if (avatar.getObjectId() == null) {
continue;
}
AvatarData avatarData = GameData.getAvatarDataMap().get(avatar.getAvatarId());
AvatarSkillDepotData skillDepot =
GameData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId());
if (avatarData == null || skillDepot == null) {
continue;
}
// Set ownerships
avatar.setAvatarData(avatarData);
avatar.setSkillDepot(skillDepot);
avatar.setOwner(getPlayer());
// Force recalc of const boosted skills
avatar.recalcConstellations();
// Add to avatar storage
this.avatars.put(avatar.getAvatarId(), avatar);
this.avatarsGuid.put(avatar.getGuid(), avatar);
}
}
public void postLoad() {
for (Avatar avatar : this) {
// Weapon check
if (avatar.getWeapon() == null) {
this.addStartingWeapon(avatar);
}
// Recalc stats
avatar.recalcStats();
}
}
@Override
public Iterator<Avatar> iterator() {
return getAvatars().values().iterator();
}
}
package emu.grasscutter.game.avatar;
import emu.grasscutter.data.GameData;
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;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.packet.send.PacketAvatarChangeCostumeNotify;
import emu.grasscutter.server.packet.send.PacketAvatarFlycloakChangeNotify;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.util.Iterator;
import java.util.List;
public class AvatarStorage extends BasePlayerManager implements Iterable<Avatar> {
private final Int2ObjectMap<Avatar> avatars;
private final Long2ObjectMap<Avatar> avatarsGuid;
public AvatarStorage(Player player) {
super(player);
this.avatars = new Int2ObjectOpenHashMap<>();
this.avatarsGuid = new Long2ObjectOpenHashMap<>();
}
public Int2ObjectMap<Avatar> getAvatars() {
return avatars;
}
public int getAvatarCount() {
return this.avatars.size();
}
public Avatar getAvatarById(int id) {
return getAvatars().get(id);
}
public Avatar getAvatarByGuid(long id) {
return avatarsGuid.get(id);
}
public boolean hasAvatar(int id) {
return getAvatars().containsKey(id);
}
public boolean addAvatar(Avatar avatar) {
if (avatar.getAvatarData() == null || this.hasAvatar(avatar.getAvatarId())) {
return false;
}
// Set owner first
avatar.setOwner(getPlayer());
// Put into maps
this.avatars.put(avatar.getAvatarId(), avatar);
this.avatarsGuid.put(avatar.getGuid(), avatar);
avatar.save();
return true;
}
public void addStartingWeapon(Avatar avatar) {
// Make sure avatar owner is this player
if (avatar.getPlayer() != this.getPlayer()) {
return;
}
// Create weapon
GameItem weapon = new GameItem(avatar.getAvatarData().getInitialWeapon());
if (weapon.getItemData() != null) {
this.getPlayer().getInventory().addItem(weapon);
avatar.equipItem(weapon, true);
}
}
public boolean wearFlycloak(long avatarGuid, int flycloakId) {
Avatar avatar = this.getAvatarByGuid(avatarGuid);
if (avatar == null || !getPlayer().getFlyCloakList().contains(flycloakId)) {
return false;
}
avatar.setFlyCloak(flycloakId);
avatar.save();
// Update
getPlayer().sendPacket(new PacketAvatarFlycloakChangeNotify(avatar));
return true;
}
public boolean changeCostume(long avatarGuid, int costumeId) {
Avatar avatar = this.getAvatarByGuid(avatarGuid);
if (avatar == null) {
return false;
}
if (costumeId != 0 && !getPlayer().getCostumeList().contains(costumeId)) {
return false;
}
// TODO make sure avatar can wear costume
avatar.setCostume(costumeId);
avatar.save();
// Update entity
EntityAvatar entity = avatar.getAsEntity();
if (entity == null) {
entity = new EntityAvatar(avatar);
getPlayer().sendPacket(new PacketAvatarChangeCostumeNotify(entity));
} else {
getPlayer().getScene().broadcastPacket(new PacketAvatarChangeCostumeNotify(entity));
}
// Done
return true;
}
public void loadFromDatabase() {
List<Avatar> avatars = DatabaseHelper.getAvatars(getPlayer());
for (Avatar avatar : avatars) {
// Should never happen
if (avatar.getObjectId() == null) {
continue;
}
AvatarData avatarData = GameData.getAvatarDataMap().get(avatar.getAvatarId());
AvatarSkillDepotData skillDepot =
GameData.getAvatarSkillDepotDataMap().get(avatar.getSkillDepotId());
if (avatarData == null || skillDepot == null) {
continue;
}
// Set ownerships
avatar.setAvatarData(avatarData);
avatar.setSkillDepot(skillDepot);
avatar.setOwner(getPlayer());
// Force recalc of const boosted skills
avatar.recalcConstellations();
// Add to avatar storage
this.avatars.put(avatar.getAvatarId(), avatar);
this.avatarsGuid.put(avatar.getGuid(), avatar);
}
}
public void postLoad() {
for (Avatar avatar : this) {
// Weapon check
if (avatar.getWeapon() == null) {
this.addStartingWeapon(avatar);
}
// Recalc stats
avatar.recalcStats();
}
}
@Override
public Iterator<Avatar> iterator() {
return getAvatars().values().iterator();
}
}

View File

@@ -1,19 +1,19 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult;
import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify;
public class BasicDungeonSettleListener implements DungeonSettleListener {
@Override
public void onDungeonSettle(
DungeonManager dungeonManager, BaseDungeonResult.DungeonEndReason endReason) {
var scene = dungeonManager.getScene();
var dungeonData = dungeonManager.getDungeonData();
var time = scene.getSceneTimeSeconds() - dungeonManager.getStartSceneTime();
// TODO time taken and chests handling
DungeonEndStats stats = new DungeonEndStats(scene.getKilledMonsterCount(), time, 0, endReason);
scene.broadcastPacket(new PacketDungeonSettleNotify(new BaseDungeonResult(dungeonData, stats)));
}
}
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult;
import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify;
public class BasicDungeonSettleListener implements DungeonSettleListener {
@Override
public void onDungeonSettle(
DungeonManager dungeonManager, BaseDungeonResult.DungeonEndReason endReason) {
var scene = dungeonManager.getScene();
var dungeonData = dungeonManager.getDungeonData();
var time = scene.getSceneTimeSeconds() - dungeonManager.getStartSceneTime();
// TODO time taken and chests handling
DungeonEndStats stats = new DungeonEndStats(scene.getKilledMonsterCount(), time, 0, endReason);
scene.broadcastPacket(new PacketDungeonSettleNotify(new BaseDungeonResult(dungeonData, stats)));
}
}

View File

@@ -1,22 +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,331 +1,331 @@
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 final 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;
}
var 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);
}
}
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 final 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;
}
var 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.dungeons.dungeon_results.BaseDungeonResult;
public interface DungeonSettleListener {
void onDungeonSettle(DungeonManager dungeonManager, BaseDungeonResult.DungeonEndReason endReason);
}
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult;
public interface DungeonSettleListener {
void onDungeonSettle(DungeonManager dungeonManager, BaseDungeonResult.DungeonEndReason endReason);
}

View File

@@ -1,170 +1,170 @@
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.dungeon.DungeonData;
import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.handlers.DungeonBaseHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.List;
import lombok.val;
import org.reflections.Reflections;
public class DungeonSystem extends BaseGameSystem {
private static final BasicDungeonSettleListener basicDungeonSettleObserver =
new BasicDungeonSettleListener();
private final Int2ObjectMap<DungeonBaseHandler> passCondHandlers;
public DungeonSystem(GameServer server) {
super(server);
this.passCondHandlers = new Int2ObjectOpenHashMap<>();
registerHandlers();
}
public void registerHandlers() {
this.registerHandlers(
this.passCondHandlers,
"emu.grasscutter.game.dungeons.pass_condition",
DungeonBaseHandler.class);
}
public <T> void registerHandlers(Int2ObjectMap<T> map, String packageName, Class<T> clazz) {
Reflections reflections = new Reflections(packageName);
var handlerClasses = reflections.getSubTypesOf(clazz);
for (var obj : handlerClasses) {
this.registerPacketHandler(map, obj);
}
}
public <T> void registerPacketHandler(Int2ObjectMap<T> map, Class<? extends T> handlerClass) {
try {
DungeonValue opcode = handlerClass.getAnnotation(DungeonValue.class);
if (opcode == null || opcode.value() == null) {
return;
}
map.put(opcode.value().ordinal(), handlerClass.getDeclaredConstructor().newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
public void getEntryInfo(Player player, int pointId) {
ScenePointEntry entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId);
if (entry == null) {
// Error
player.sendPacket(new PacketDungeonEntryInfoRsp());
return;
}
player.sendPacket(new PacketDungeonEntryInfoRsp(player, entry.getPointData()));
}
public boolean triggerCondition(
DungeonPassConfigData.DungeonPassCondition condition, int... params) {
var handler = passCondHandlers.get(condition.getCondType().ordinal());
if (handler == null) {
Grasscutter.getLogger()
.debug("Could not trigger condition {} at {}", condition.getCondType(), params);
return false;
}
return handler.execute(condition, params);
}
public boolean enterDungeon(Player player, int pointId, int dungeonId) {
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
if (data == null) {
return false;
}
Grasscutter.getLogger()
.info(
"{}({}) is trying to enter dungeon {}",
player.getNickname(),
player.getUid(),
dungeonId);
int sceneId = data.getSceneId();
var scene = player.getScene();
scene.setPrevScene(sceneId);
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
scene = player.getScene();
scene.addDungeonSettleObserver(basicDungeonSettleObserver);
}
scene.setPrevScenePoint(pointId);
return true;
}
/** used in tower dungeons handoff */
public boolean handoffDungeon(
Player player, int dungeonId, List<DungeonSettleListener> dungeonSettleListeners) {
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
if (data == null) {
return false;
}
Grasscutter.getLogger()
.info(
"{}({}) is trying to enter tower dungeon {}",
player.getNickname(),
player.getUid(),
dungeonId);
if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) {
dungeonSettleListeners.forEach(player.getScene()::addDungeonSettleObserver);
}
return true;
}
public void exitDungeon(Player player) {
Scene scene = player.getScene();
if (scene == null || scene.getSceneType() != SceneType.SCENE_DUNGEON) {
return;
}
// Get previous scene
int prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3;
// Get previous position
val dungeonManager = scene.getDungeonManager();
DungeonData dungeonData = dungeonManager != null ? dungeonManager.getDungeonData() : null;
Position prevPos = new Position(GameConstants.START_POSITION);
if (dungeonData != null) {
ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint());
if (entry != null) {
prevPos.set(entry.getPointData().getTranPos());
}
if (!dungeonManager.isFinishedSuccessfully()) {
dungeonManager.quitDungeon();
}
dungeonManager.unsetTrialTeam(player);
}
// clean temp team if it has
player.getTeamManager().cleanTemporaryTeam();
player.getTowerManager().clearEntry();
// Transfer player back to world
player.getWorld().transferPlayerToScene(player, prevScene, prevPos);
}
}
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.dungeon.DungeonData;
import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData;
import emu.grasscutter.game.dungeons.handlers.DungeonBaseHandler;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.SceneType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.server.game.BaseGameSystem;
import emu.grasscutter.server.game.GameServer;
import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.List;
import lombok.val;
import org.reflections.Reflections;
public class DungeonSystem extends BaseGameSystem {
private static final BasicDungeonSettleListener basicDungeonSettleObserver =
new BasicDungeonSettleListener();
private final Int2ObjectMap<DungeonBaseHandler> passCondHandlers;
public DungeonSystem(GameServer server) {
super(server);
this.passCondHandlers = new Int2ObjectOpenHashMap<>();
registerHandlers();
}
public void registerHandlers() {
this.registerHandlers(
this.passCondHandlers,
"emu.grasscutter.game.dungeons.pass_condition",
DungeonBaseHandler.class);
}
public <T> void registerHandlers(Int2ObjectMap<T> map, String packageName, Class<T> clazz) {
Reflections reflections = new Reflections(packageName);
var handlerClasses = reflections.getSubTypesOf(clazz);
for (var obj : handlerClasses) {
this.registerPacketHandler(map, obj);
}
}
public <T> void registerPacketHandler(Int2ObjectMap<T> map, Class<? extends T> handlerClass) {
try {
DungeonValue opcode = handlerClass.getAnnotation(DungeonValue.class);
if (opcode == null || opcode.value() == null) {
return;
}
map.put(opcode.value().ordinal(), handlerClass.getDeclaredConstructor().newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
public void getEntryInfo(Player player, int pointId) {
ScenePointEntry entry = GameData.getScenePointEntryById(player.getScene().getId(), pointId);
if (entry == null) {
// Error
player.sendPacket(new PacketDungeonEntryInfoRsp());
return;
}
player.sendPacket(new PacketDungeonEntryInfoRsp(player, entry.getPointData()));
}
public boolean triggerCondition(
DungeonPassConfigData.DungeonPassCondition condition, int... params) {
var handler = passCondHandlers.get(condition.getCondType().ordinal());
if (handler == null) {
Grasscutter.getLogger()
.debug("Could not trigger condition {} at {}", condition.getCondType(), params);
return false;
}
return handler.execute(condition, params);
}
public boolean enterDungeon(Player player, int pointId, int dungeonId) {
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
if (data == null) {
return false;
}
Grasscutter.getLogger()
.info(
"{}({}) is trying to enter dungeon {}",
player.getNickname(),
player.getUid(),
dungeonId);
int sceneId = data.getSceneId();
var scene = player.getScene();
scene.setPrevScene(sceneId);
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
scene = player.getScene();
scene.addDungeonSettleObserver(basicDungeonSettleObserver);
}
scene.setPrevScenePoint(pointId);
return true;
}
/** used in tower dungeons handoff */
public boolean handoffDungeon(
Player player, int dungeonId, List<DungeonSettleListener> dungeonSettleListeners) {
DungeonData data = GameData.getDungeonDataMap().get(dungeonId);
if (data == null) {
return false;
}
Grasscutter.getLogger()
.info(
"{}({}) is trying to enter tower dungeon {}",
player.getNickname(),
player.getUid(),
dungeonId);
if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) {
dungeonSettleListeners.forEach(player.getScene()::addDungeonSettleObserver);
}
return true;
}
public void exitDungeon(Player player) {
Scene scene = player.getScene();
if (scene == null || scene.getSceneType() != SceneType.SCENE_DUNGEON) {
return;
}
// Get previous scene
int prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3;
// Get previous position
val dungeonManager = scene.getDungeonManager();
DungeonData dungeonData = dungeonManager != null ? dungeonManager.getDungeonData() : null;
Position prevPos = new Position(GameConstants.START_POSITION);
if (dungeonData != null) {
ScenePointEntry entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint());
if (entry != null) {
prevPos.set(entry.getPointData().getTranPos());
}
if (!dungeonManager.isFinishedSuccessfully()) {
dungeonManager.quitDungeon();
}
dungeonManager.unsetTrialTeam(player);
}
// clean temp team if it has
player.getTeamManager().cleanTemporaryTeam();
player.getTowerManager().clearEntry();
// Transfer player back to world
player.getWorld().transferPlayerToScene(player, prevScene, prevPos);
}
}

View File

@@ -1,13 +1,13 @@
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;
}
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,10 +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,40 +1,40 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult.DungeonEndReason;
import emu.grasscutter.game.dungeons.dungeon_results.TowerResult;
import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify;
import emu.grasscutter.server.packet.send.PacketTowerFloorRecordChangeNotify;
public class TowerDungeonSettleListener implements DungeonSettleListener {
@Override
public void onDungeonSettle(DungeonManager dungeonManager, DungeonEndReason endReason) {
var scene = dungeonManager.getScene();
var dungeonData = dungeonManager.getDungeonData();
if (scene.getLoadedGroups().stream()
.anyMatch(
g -> {
var variables = scene.getScriptManager().getVariables(g.id);
return variables != null
&& variables.containsKey("stage")
&& variables.get("stage") == 1;
})) {
return;
}
var towerManager = scene.getPlayers().get(0).getTowerManager();
towerManager.notifyCurLevelRecordChangeWhenDone(3);
scene.broadcastPacket(
new PacketTowerFloorRecordChangeNotify(
towerManager.getCurrentFloorId(), 3, towerManager.canEnterScheduleFloor()));
var challenge = scene.getChallenge();
var dungeonStats =
new DungeonEndStats(
scene.getKilledMonsterCount(), challenge.getFinishedTime(), 0, endReason);
var result = new TowerResult(dungeonData, dungeonStats, towerManager, challenge);
scene.broadcastPacket(new PacketDungeonSettleNotify(result));
}
}
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult.DungeonEndReason;
import emu.grasscutter.game.dungeons.dungeon_results.TowerResult;
import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify;
import emu.grasscutter.server.packet.send.PacketTowerFloorRecordChangeNotify;
public class TowerDungeonSettleListener implements DungeonSettleListener {
@Override
public void onDungeonSettle(DungeonManager dungeonManager, DungeonEndReason endReason) {
var scene = dungeonManager.getScene();
var dungeonData = dungeonManager.getDungeonData();
if (scene.getLoadedGroups().stream()
.anyMatch(
g -> {
var variables = scene.getScriptManager().getVariables(g.id);
return variables != null
&& variables.containsKey("stage")
&& variables.get("stage") == 1;
})) {
return;
}
var towerManager = scene.getPlayers().get(0).getTowerManager();
towerManager.notifyCurLevelRecordChangeWhenDone(3);
scene.broadcastPacket(
new PacketTowerFloorRecordChangeNotify(
towerManager.getCurrentFloorId(), 3, towerManager.canEnterScheduleFloor()));
var challenge = scene.getChallenge();
var dungeonStats =
new DungeonEndStats(
scene.getKilledMonsterCount(), challenge.getFinishedTime(), 0, endReason);
var result = new TowerResult(dungeonData, dungeonStats, towerManager, challenge);
scene.broadcastPacket(new PacketDungeonSettleNotify(result));
}
}

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,44 +1,44 @@
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.ArrayList;
import java.util.List;
import lombok.val;
public abstract class ChallengeFactory {
private static final List<ChallengeFactoryHandler> challengeFactoryHandlers = new ArrayList<>();
static {
challengeFactoryHandlers.add(new KillAndGuardChallengeFactoryHandler());
challengeFactoryHandlers.add(new KillMonsterCountChallengeFactoryHandler());
challengeFactoryHandlers.add(new KillMonsterInTimeChallengeFactoryHandler());
challengeFactoryHandlers.add(new KillMonsterTimeChallengeFactoryHandler());
challengeFactoryHandlers.add(new SurviveChallengeFactoryHandler());
challengeFactoryHandlers.add(new TriggerInTimeChallengeFactoryHandler());
}
public static WorldChallenge getChallenge(
int localChallengeId,
int challengeDataId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group) {
val challengeData = GameData.getDungeonChallengeConfigDataMap().get(challengeDataId);
val challengeType = challengeData.getChallengeType();
for (var handler : challengeFactoryHandlers) {
if (!handler.isThisType(challengeType)) {
continue;
}
return handler.build(
localChallengeId, challengeDataId, param3, param4, param5, param6, scene, group);
}
return null;
}
}
package emu.grasscutter.game.dungeons.challenge.factory;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.ArrayList;
import java.util.List;
import lombok.val;
public abstract class ChallengeFactory {
private static final List<ChallengeFactoryHandler> challengeFactoryHandlers = new ArrayList<>();
static {
challengeFactoryHandlers.add(new KillAndGuardChallengeFactoryHandler());
challengeFactoryHandlers.add(new KillMonsterCountChallengeFactoryHandler());
challengeFactoryHandlers.add(new KillMonsterInTimeChallengeFactoryHandler());
challengeFactoryHandlers.add(new KillMonsterTimeChallengeFactoryHandler());
challengeFactoryHandlers.add(new SurviveChallengeFactoryHandler());
challengeFactoryHandlers.add(new TriggerInTimeChallengeFactoryHandler());
}
public static WorldChallenge getChallenge(
int localChallengeId,
int challengeDataId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group) {
val challengeData = GameData.getDungeonChallengeConfigDataMap().get(challengeDataId);
val challengeType = challengeData.getChallengeType();
for (var handler : challengeFactoryHandlers) {
if (!handler.isThisType(challengeType)) {
continue;
}
return handler.build(
localChallengeId, challengeDataId, param3, param4, param5, param6, scene, group);
}
return null;
}
}

View File

@@ -1,20 +1,20 @@
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.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
public interface ChallengeFactoryHandler {
boolean isThisType(ChallengeType challengeType);
WorldChallenge build(
int challengeIndex,
int challengeId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group);
}
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.world.Scene;
import emu.grasscutter.scripts.data.SceneGroup;
public interface ChallengeFactoryHandler {
boolean isThisType(ChallengeType challengeType);
WorldChallenge build(
int challengeIndex,
int challengeId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group);
}

View File

@@ -1,42 +1,42 @@
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)));
}
}
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,39 +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 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()));
}
}
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,40 +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 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()));
}
}
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,42 +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 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()));
}
}
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,40 +1,40 @@
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()));
}
}
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,43 +1,43 @@
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))));
}
}
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,22 +1,22 @@
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.scripts.data.SceneTrigger;
public abstract class ChallengeTrigger {
public void onBegin(WorldChallenge challenge) {}
public void onFinish(WorldChallenge challenge) {}
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {}
public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget) {}
public void onCheckTimeout(WorldChallenge challenge) {}
public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) {}
public void onGroupTrigger(WorldChallenge challenge, SceneTrigger trigger) {}
}
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.scripts.data.SceneTrigger;
public abstract class ChallengeTrigger {
public void onBegin(WorldChallenge challenge) {}
public void onFinish(WorldChallenge challenge) {}
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {}
public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget) {}
public void onCheckTimeout(WorldChallenge challenge) {}
public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) {}
public void onGroupTrigger(WorldChallenge challenge, SceneTrigger trigger) {}
}

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,38 +1,38 @@
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
public class GuardTrigger extends ChallengeTrigger {
private final int entityToProtectCFGId;
private int lastSendPercent = 100;
public GuardTrigger(int entityToProtectCFGId) {
this.entityToProtectCFGId = entityToProtectCFGId;
}
public void onBegin(WorldChallenge challenge) {
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, 100));
}
@Override
public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) {
if (gadget.getConfigId() != entityToProtectCFGId) {
return;
}
var curHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_CUR_HP.getId());
var maxHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_BASE_HP.getId());
int percent = (int) (curHp / maxHp);
if (percent != lastSendPercent) {
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, percent));
lastSendPercent = percent;
}
if (percent <= 0) {
challenge.fail();
}
}
}
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
public class GuardTrigger extends ChallengeTrigger {
private final int entityToProtectCFGId;
private int lastSendPercent = 100;
public GuardTrigger(int entityToProtectCFGId) {
this.entityToProtectCFGId = entityToProtectCFGId;
}
public void onBegin(WorldChallenge challenge) {
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, 100));
}
@Override
public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) {
if (gadget.getConfigId() != entityToProtectCFGId) {
return;
}
var curHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_CUR_HP.getId());
var maxHp = gadget.getFightProperties().get(FightProperty.FIGHT_PROP_BASE_HP.getId());
int percent = (int) (curHp / maxHp);
if (percent != lastSendPercent) {
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, percent));
lastSendPercent = percent;
}
if (percent <= 0) {
challenge.fail();
}
}
}

View File

@@ -1,24 +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,25 +1,25 @@
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;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class KillMonsterTrigger extends ChallengeTrigger {
private int monsterCfgId;
@Override
public void onBegin(WorldChallenge challenge) {
challenge
.getScene()
.broadcastPacket(new PacketChallengeDataNotify(challenge, 1, challenge.getScore().get()));
}
@Override
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {
if (monster.getConfigId() == monsterCfgId) {
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;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class KillMonsterTrigger extends ChallengeTrigger {
private int monsterCfgId;
@Override
public void onBegin(WorldChallenge challenge) {
challenge
.getScene()
.broadcastPacket(new PacketChallengeDataNotify(challenge, 1, challenge.getScore().get()));
}
@Override
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {
if (monster.getConfigId() == monsterCfgId) {
challenge.done();
}
}
}

View File

@@ -1,31 +1,31 @@
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();
}
}
}
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,77 +1,77 @@
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;
}
}
}
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,54 +1,54 @@
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);
}
}
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,26 +1,26 @@
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));
}
}
}
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,28 +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,8 +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.dungeon.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,16 +1,16 @@
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;
}
}
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.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];
}
}
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.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];
}
}
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.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];
}
}
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.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];
}
}
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.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];
}
}
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.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];
}
}
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.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];
}
}
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

@@ -1,308 +1,308 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.config.ConfigEntityGadget;
import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.entity.gadget.*;
import emu.grasscutter.game.entity.gadget.platform.BaseRoute;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.SceneGroupInstance;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PlatformInfoOuterClass;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.scripts.EntityControllerScriptManager;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketGadgetStateNotify;
import emu.grasscutter.server.packet.send.PacketPlatformStartRouteNotify;
import emu.grasscutter.server.packet.send.PacketPlatformStopRouteNotify;
import emu.grasscutter.server.packet.send.PacketSceneTimeNotify;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString(callSuper = true)
public class EntityGadget extends EntityBaseGadget {
@Getter private final GadgetData gadgetData;
@Getter(onMethod = @__(@Override))
@Setter
private int gadgetId;
@Getter private final Position bornPos;
@Getter private final Position bornRot;
@Getter @Setter private GameEntity owner = null;
@Getter @Setter private List<GameEntity> children = new ArrayList<>();
@Getter private int state;
@Getter @Setter private int pointType;
@Getter private GadgetContent content;
@Getter(onMethod = @__(@Override), lazy = true)
private final Int2FloatMap fightProperties = new Int2FloatOpenHashMap();
@Getter @Setter private SceneGadget metaGadget;
@Nullable @Getter private ConfigEntityGadget configGadget;
@Getter @Setter private BaseRoute routeConfig;
@Getter @Setter private int stopValue = 0; // Controller related, inited to zero
@Getter @Setter private int startValue = 0; // Controller related, inited to zero
@Getter @Setter private int ticksSinceChange;
public EntityGadget(Scene scene, int gadgetId, Position pos) {
this(scene, gadgetId, pos, null, null);
}
public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot) {
this(scene, gadgetId, pos, rot, null);
}
public EntityGadget(
Scene scene, int gadgetId, Position pos, Position rot, GadgetContent content) {
super(scene, pos, rot);
this.gadgetData = GameData.getGadgetDataMap().get(gadgetId);
if (gadgetData != null && gadgetData.getJsonName() != null) {
this.configGadget = GameData.getGadgetConfigData().get(gadgetData.getJsonName());
}
this.id = this.getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.gadgetId = gadgetId;
this.content = content;
this.bornPos = this.getPosition().clone();
this.bornRot = this.getRotation().clone();
this.fillFightProps(configGadget);
if (GameData.getGadgetMappingMap().containsKey(gadgetId)) {
String controllerName = GameData.getGadgetMappingMap().get(gadgetId).getServerController();
this.setEntityController(EntityControllerScriptManager.getGadgetController(controllerName));
}
}
public void setState(int state) {
this.state = state;
// Cache the gadget state
if (metaGadget != null && metaGadget.group != null) {
var instance = getScene().getScriptManager().getCachedGroupInstanceById(metaGadget.group.id);
if (instance != null) instance.cacheGadgetState(metaGadget, state);
}
}
public void updateState(int state) {
if (state == this.getState()) return; // Don't triggers events
this.setState(state);
ticksSinceChange = getScene().getSceneTimeSeconds();
this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, state));
getScene()
.getScriptManager()
.callEvent(
new ScriptArgs(
this.getGroupId(), EventType.EVENT_GADGET_STATE_CHANGE, state, this.getConfigId()));
}
@Deprecated(forRemoval = true) // Dont use!
public void setContent(GadgetContent content) {
this.content = this.content == null ? content : this.content;
}
// TODO refactor
public void buildContent() {
if (this.getContent() != null
|| this.getGadgetData() == null
|| this.getGadgetData().getType() == null) {
return;
}
this.content =
switch (this.getGadgetData().getType()) {
case GatherPoint -> new GadgetGatherPoint(this);
case GatherObject -> new GadgetGatherObject(this);
case Worktop, SealGadget -> new GadgetWorktop(this);
case RewardStatue -> new GadgetRewardStatue(this);
case Chest -> new GadgetChest(this);
case Gadget -> new GadgetObject(this);
default -> null;
};
}
@Override
public void onInteract(Player player, GadgetInteractReq interactReq) {
if (this.getContent() == null) {
return;
}
boolean shouldDelete = this.getContent().onInteract(player, interactReq);
if (shouldDelete) {
this.getScene().killEntity(this);
}
}
@Override
public void onCreate() {
// Lua event
getScene()
.getScriptManager()
.callEvent(
new ScriptArgs(this.getGroupId(), EventType.EVENT_GADGET_CREATE, this.getConfigId()));
}
@Override
public void onRemoved() {
super.onRemoved();
if (!children.isEmpty()) {
getScene().removeEntities(children, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE);
children.clear();
}
}
@Override
public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
if (this.getSpawnEntry() != null) {
this.getScene().getDeadSpawnedEntities().add(getSpawnEntry());
}
if (getScene().getChallenge() != null) {
getScene().getChallenge().onGadgetDeath(this);
}
getScene()
.getScriptManager()
.callEvent(
new ScriptArgs(this.getGroupId(), EventType.EVENT_ANY_GADGET_DIE, this.getConfigId()));
SceneGroupInstance groupInstance =
getScene().getScriptManager().getCachedGroupInstanceById(this.getGroupId());
if (groupInstance != null && metaGadget != null)
groupInstance.getDeadEntities().add(metaGadget.config_id);
}
public boolean startPlatform() {
if (routeConfig == null) {
return false;
}
if (routeConfig.isStarted()) {
return true;
}
getScene().broadcastPacket(new PacketSceneTimeNotify(getScene()));
routeConfig.startRoute(getScene());
getScene().broadcastPacket(new PacketPlatformStartRouteNotify(this));
return true;
}
public boolean stopPlatform() {
if (routeConfig == null) {
return false;
}
if (!routeConfig.isStarted()) {
return true;
}
routeConfig.stopRoute(getScene());
getScene().broadcastPacket(new PacketPlatformStopRouteNotify(this));
return true;
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority =
EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(bornPos.toProto()))
.setBornPos(bornPos.toProto())
.build();
SceneEntityInfo.Builder entityInfo =
SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(
MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair =
PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
.build();
entityInfo.addPropList(pair);
// We do not use the getter to null check because the getter will create a fight prop map if it
// is null
if (this.fightProperties != null) {
addAllFightPropsToEntityInfo(entityInfo);
}
SceneGadgetInfo.Builder gadgetInfo =
SceneGadgetInfo.newBuilder()
.setGadgetId(this.getGadgetId())
.setGroupId(this.getGroupId())
.setConfigId(this.getConfigId())
.setGadgetState(this.getState())
.setIsEnableInteract(true)
.setAuthorityPeerId(this.getScene().getWorld().getHostPeerId());
if (this.metaGadget != null) {
gadgetInfo.setDraftId(this.metaGadget.draft_id);
}
if (owner != null) {
gadgetInfo.setOwnerEntityId(owner.getId());
}
if (this.getContent() != null) {
this.getContent().onBuildProto(gadgetInfo);
}
if (routeConfig != null) {
gadgetInfo.setPlatform(getPlatformInfo());
}
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
public PlatformInfoOuterClass.PlatformInfo.Builder getPlatformInfo() {
if (routeConfig != null) {
return routeConfig.toProto();
}
return PlatformInfoOuterClass.PlatformInfo.newBuilder();
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.config.ConfigEntityGadget;
import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.entity.gadget.*;
import emu.grasscutter.game.entity.gadget.platform.BaseRoute;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.SceneGroupInstance;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityClientDataOuterClass.EntityClientData;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PlatformInfoOuterClass;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.net.proto.VisionTypeOuterClass;
import emu.grasscutter.scripts.EntityControllerScriptManager;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketGadgetStateNotify;
import emu.grasscutter.server.packet.send.PacketPlatformStartRouteNotify;
import emu.grasscutter.server.packet.send.PacketPlatformStopRouteNotify;
import emu.grasscutter.server.packet.send.PacketSceneTimeNotify;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString(callSuper = true)
public class EntityGadget extends EntityBaseGadget {
@Getter private final GadgetData gadgetData;
@Getter(onMethod = @__(@Override))
@Setter
private int gadgetId;
@Getter private final Position bornPos;
@Getter private final Position bornRot;
@Getter @Setter private GameEntity owner = null;
@Getter @Setter private List<GameEntity> children = new ArrayList<>();
@Getter private int state;
@Getter @Setter private int pointType;
@Getter private GadgetContent content;
@Getter(onMethod = @__(@Override), lazy = true)
private final Int2FloatMap fightProperties = new Int2FloatOpenHashMap();
@Getter @Setter private SceneGadget metaGadget;
@Nullable @Getter private ConfigEntityGadget configGadget;
@Getter @Setter private BaseRoute routeConfig;
@Getter @Setter private int stopValue = 0; // Controller related, inited to zero
@Getter @Setter private int startValue = 0; // Controller related, inited to zero
@Getter @Setter private int ticksSinceChange;
public EntityGadget(Scene scene, int gadgetId, Position pos) {
this(scene, gadgetId, pos, null, null);
}
public EntityGadget(Scene scene, int gadgetId, Position pos, Position rot) {
this(scene, gadgetId, pos, rot, null);
}
public EntityGadget(
Scene scene, int gadgetId, Position pos, Position rot, GadgetContent content) {
super(scene, pos, rot);
this.gadgetData = GameData.getGadgetDataMap().get(gadgetId);
if (gadgetData != null && gadgetData.getJsonName() != null) {
this.configGadget = GameData.getGadgetConfigData().get(gadgetData.getJsonName());
}
this.id = this.getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.gadgetId = gadgetId;
this.content = content;
this.bornPos = this.getPosition().clone();
this.bornRot = this.getRotation().clone();
this.fillFightProps(configGadget);
if (GameData.getGadgetMappingMap().containsKey(gadgetId)) {
String controllerName = GameData.getGadgetMappingMap().get(gadgetId).getServerController();
this.setEntityController(EntityControllerScriptManager.getGadgetController(controllerName));
}
}
public void setState(int state) {
this.state = state;
// Cache the gadget state
if (metaGadget != null && metaGadget.group != null) {
var instance = getScene().getScriptManager().getCachedGroupInstanceById(metaGadget.group.id);
if (instance != null) instance.cacheGadgetState(metaGadget, state);
}
}
public void updateState(int state) {
if (state == this.getState()) return; // Don't triggers events
this.setState(state);
ticksSinceChange = getScene().getSceneTimeSeconds();
this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, state));
getScene()
.getScriptManager()
.callEvent(
new ScriptArgs(
this.getGroupId(), EventType.EVENT_GADGET_STATE_CHANGE, state, this.getConfigId()));
}
@Deprecated(forRemoval = true) // Dont use!
public void setContent(GadgetContent content) {
this.content = this.content == null ? content : this.content;
}
// TODO refactor
public void buildContent() {
if (this.getContent() != null
|| this.getGadgetData() == null
|| this.getGadgetData().getType() == null) {
return;
}
this.content =
switch (this.getGadgetData().getType()) {
case GatherPoint -> new GadgetGatherPoint(this);
case GatherObject -> new GadgetGatherObject(this);
case Worktop, SealGadget -> new GadgetWorktop(this);
case RewardStatue -> new GadgetRewardStatue(this);
case Chest -> new GadgetChest(this);
case Gadget -> new GadgetObject(this);
default -> null;
};
}
@Override
public void onInteract(Player player, GadgetInteractReq interactReq) {
if (this.getContent() == null) {
return;
}
boolean shouldDelete = this.getContent().onInteract(player, interactReq);
if (shouldDelete) {
this.getScene().killEntity(this);
}
}
@Override
public void onCreate() {
// Lua event
getScene()
.getScriptManager()
.callEvent(
new ScriptArgs(this.getGroupId(), EventType.EVENT_GADGET_CREATE, this.getConfigId()));
}
@Override
public void onRemoved() {
super.onRemoved();
if (!children.isEmpty()) {
getScene().removeEntities(children, VisionTypeOuterClass.VisionType.VISION_TYPE_REMOVE);
children.clear();
}
}
@Override
public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
if (this.getSpawnEntry() != null) {
this.getScene().getDeadSpawnedEntities().add(getSpawnEntry());
}
if (getScene().getChallenge() != null) {
getScene().getChallenge().onGadgetDeath(this);
}
getScene()
.getScriptManager()
.callEvent(
new ScriptArgs(this.getGroupId(), EventType.EVENT_ANY_GADGET_DIE, this.getConfigId()));
SceneGroupInstance groupInstance =
getScene().getScriptManager().getCachedGroupInstanceById(this.getGroupId());
if (groupInstance != null && metaGadget != null)
groupInstance.getDeadEntities().add(metaGadget.config_id);
}
public boolean startPlatform() {
if (routeConfig == null) {
return false;
}
if (routeConfig.isStarted()) {
return true;
}
getScene().broadcastPacket(new PacketSceneTimeNotify(getScene()));
routeConfig.startRoute(getScene());
getScene().broadcastPacket(new PacketPlatformStartRouteNotify(this));
return true;
}
public boolean stopPlatform() {
if (routeConfig == null) {
return false;
}
if (!routeConfig.isStarted()) {
return true;
}
routeConfig.stopRoute(getScene());
getScene().broadcastPacket(new PacketPlatformStopRouteNotify(this));
return true;
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority =
EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(bornPos.toProto()))
.setBornPos(bornPos.toProto())
.build();
SceneEntityInfo.Builder entityInfo =
SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(
MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair =
PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 1))
.build();
entityInfo.addPropList(pair);
// We do not use the getter to null check because the getter will create a fight prop map if it
// is null
if (this.fightProperties != null) {
addAllFightPropsToEntityInfo(entityInfo);
}
SceneGadgetInfo.Builder gadgetInfo =
SceneGadgetInfo.newBuilder()
.setGadgetId(this.getGadgetId())
.setGroupId(this.getGroupId())
.setConfigId(this.getConfigId())
.setGadgetState(this.getState())
.setIsEnableInteract(true)
.setAuthorityPeerId(this.getScene().getWorld().getHostPeerId());
if (this.metaGadget != null) {
gadgetInfo.setDraftId(this.metaGadget.draft_id);
}
if (owner != null) {
gadgetInfo.setOwnerEntityId(owner.getId());
}
if (this.getContent() != null) {
this.getContent().onBuildProto(gadgetInfo);
}
if (routeConfig != null) {
gadgetInfo.setPlatform(getPlatformInfo());
}
entityInfo.setGadget(gadgetInfo);
return entityInfo.build();
}
public PlatformInfoOuterClass.PlatformInfo.Builder getPlatformInfo() {
if (routeConfig != null) {
return routeConfig.toProto();
}
return PlatformInfoOuterClass.PlatformInfo.newBuilder();
}
}

View File

@@ -1,82 +1,82 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.scripts.data.SceneNPC;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import lombok.Getter;
public class EntityNPC extends GameEntity {
@Getter(onMethod_ = @Override)
private final Position position;
@Getter(onMethod_ = @Override)
private final Position rotation;
private final SceneNPC metaNpc;
@Getter private final int suiteId;
public EntityNPC(Scene scene, SceneNPC metaNPC, int blockId, int suiteId) {
super(scene);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.NPC);
setConfigId(metaNPC.config_id);
setGroupId(metaNPC.group.id);
setBlockId(blockId);
this.suiteId = suiteId;
this.position = metaNPC.pos.clone();
this.rotation = metaNPC.rot.clone();
this.metaNpc = metaNPC;
}
@Override
public int getEntityTypeId() {
return this.metaNpc.npc_id;
}
@Override
public Int2FloatMap getFightProperties() {
return null;
}
@Override
public SceneEntityInfoOuterClass.SceneEntityInfo toProto() {
EntityAuthorityInfoOuterClass.EntityAuthorityInfo authority =
EntityAuthorityInfoOuterClass.EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(
EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfoOuterClass.SceneEntityAiInfo.newBuilder()
.setIsAiOpen(true)
.setBornPos(getPosition().toProto()))
.setBornPos(getPosition().toProto())
.build();
SceneEntityInfoOuterClass.SceneEntityInfo.Builder entityInfo =
SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_TYPE_NPC)
.setMotionInfo(
MotionInfoOuterClass.MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(VectorOuterClass.Vector.newBuilder()))
.addAnimatorParaList(
AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair
.newBuilder())
.setEntityClientData(EntityClientDataOuterClass.EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
entityInfo.setNpc(
SceneNpcInfoOuterClass.SceneNpcInfo.newBuilder()
.setNpcId(metaNpc.npc_id)
.setBlockId(getBlockId())
.build());
return entityInfo.build();
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.scripts.data.SceneNPC;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import lombok.Getter;
public class EntityNPC extends GameEntity {
@Getter(onMethod_ = @Override)
private final Position position;
@Getter(onMethod_ = @Override)
private final Position rotation;
private final SceneNPC metaNpc;
@Getter private final int suiteId;
public EntityNPC(Scene scene, SceneNPC metaNPC, int blockId, int suiteId) {
super(scene);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.NPC);
setConfigId(metaNPC.config_id);
setGroupId(metaNPC.group.id);
setBlockId(blockId);
this.suiteId = suiteId;
this.position = metaNPC.pos.clone();
this.rotation = metaNPC.rot.clone();
this.metaNpc = metaNPC;
}
@Override
public int getEntityTypeId() {
return this.metaNpc.npc_id;
}
@Override
public Int2FloatMap getFightProperties() {
return null;
}
@Override
public SceneEntityInfoOuterClass.SceneEntityInfo toProto() {
EntityAuthorityInfoOuterClass.EntityAuthorityInfo authority =
EntityAuthorityInfoOuterClass.EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(
EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfoOuterClass.SceneEntityAiInfo.newBuilder()
.setIsAiOpen(true)
.setBornPos(getPosition().toProto()))
.setBornPos(getPosition().toProto())
.build();
SceneEntityInfoOuterClass.SceneEntityInfo.Builder entityInfo =
SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_TYPE_NPC)
.setMotionInfo(
MotionInfoOuterClass.MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(VectorOuterClass.Vector.newBuilder()))
.addAnimatorParaList(
AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair
.newBuilder())
.setEntityClientData(EntityClientDataOuterClass.EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLifeState(1);
entityInfo.setNpc(
SceneNpcInfoOuterClass.SceneNpcInfo.newBuilder()
.setNpcId(metaNpc.npc_id)
.setBlockId(getBlockId())
.build());
return entityInfo.build();
}
}

View File

@@ -1,33 +1,33 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.game.entity.platform.EntitySolarIsotomaElevatorPlatform;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.EvtCreateGadgetNotifyOuterClass;
import lombok.Getter;
public class EntitySolarIsotomaClientGadget extends EntityClientGadget {
public static final int GADGET_ID = 41038001;
public static final int ELEVATOR_GADGET_ID = 41038002;
@Getter private EntityGadget platformGadget;
public EntitySolarIsotomaClientGadget(
Scene scene, Player player, EvtCreateGadgetNotifyOuterClass.EvtCreateGadgetNotify notify) {
super(scene, player, notify);
}
@Override
public void onCreate() {
// Create solar isotoma elevator and send to all.
this.platformGadget =
new EntitySolarIsotomaElevatorPlatform(
this, getScene(), ELEVATOR_GADGET_ID, getPosition(), getRotation());
getScene().addEntity(this.platformGadget);
}
@Override
public void onRemoved() {
// Remove solar isotoma elevator entity.
getScene().removeEntity(this.platformGadget);
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.game.entity.platform.EntitySolarIsotomaElevatorPlatform;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.EvtCreateGadgetNotifyOuterClass;
import lombok.Getter;
public class EntitySolarIsotomaClientGadget extends EntityClientGadget {
public static final int GADGET_ID = 41038001;
public static final int ELEVATOR_GADGET_ID = 41038002;
@Getter private EntityGadget platformGadget;
public EntitySolarIsotomaClientGadget(
Scene scene, Player player, EvtCreateGadgetNotifyOuterClass.EvtCreateGadgetNotify notify) {
super(scene, player, notify);
}
@Override
public void onCreate() {
// Create solar isotoma elevator and send to all.
this.platformGadget =
new EntitySolarIsotomaElevatorPlatform(
this, getScene(), ELEVATOR_GADGET_ID, getPosition(), getRotation());
getScene().addEntity(this.platformGadget);
}
@Override
public void onRemoved() {
// Remove solar isotoma elevator entity.
getScene().removeEntity(this.platformGadget);
}
}

View File

@@ -1,125 +1,125 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.config.ConfigEntityGadget;
import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.net.proto.VehicleInfoOuterClass.VehicleInfo;
import emu.grasscutter.net.proto.VehicleMemberOuterClass.VehicleMember;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.Setter;
public class EntityVehicle extends EntityBaseGadget {
@Getter private final Player owner;
@Getter(onMethod = @__(@Override))
private final Int2FloatMap fightProperties;
@Getter private final int pointId;
@Getter private final int gadgetId;
@Getter @Setter private float curStamina;
@Getter private final List<VehicleMember> vehicleMembers;
@Nullable @Getter private ConfigEntityGadget configGadget;
public EntityVehicle(
Scene scene, Player player, int gadgetId, int pointId, Position pos, Position rot) {
super(scene, pos, rot);
this.owner = player;
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.fightProperties = new Int2FloatOpenHashMap();
this.gadgetId = gadgetId;
this.pointId = pointId;
this.curStamina = 240; // might be in configGadget.GCALKECLLLP.JBAKBEFIMBN.ANBMPHPOALP
this.vehicleMembers = new ArrayList<>();
GadgetData data = GameData.getGadgetDataMap().get(gadgetId);
if (data != null && data.getJsonName() != null) {
this.configGadget = GameData.getGadgetConfigData().get(data.getJsonName());
}
fillFightProps(configGadget);
}
@Override
protected void fillFightProps(ConfigEntityGadget configGadget) {
super.fillFightProps(configGadget);
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_SPEED, 0);
this.addFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, 0);
}
@Override
public SceneEntityInfo toProto() {
VehicleInfo vehicle =
VehicleInfo.newBuilder()
.setOwnerUid(this.owner.getUid())
.setCurStamina(getCurStamina())
.build();
EntityAuthorityInfo authority =
EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfo.newBuilder()
.setIsAiOpen(true)
.setBornPos(getPosition().toProto()))
.setBornPos(getPosition().toProto())
.build();
SceneGadgetInfo.Builder gadgetInfo =
SceneGadgetInfo.newBuilder()
.setGadgetId(this.getGadgetId())
.setAuthorityPeerId(this.getOwner().getPeerId())
.setIsEnableInteract(true)
.setVehicleInfo(vehicle);
SceneEntityInfo.Builder entityInfo =
SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(
MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setGadget(gadgetInfo)
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair =
PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 47))
.build();
this.addAllFightPropsToEntityInfo(entityInfo);
entityInfo.addPropList(pair);
return entityInfo.build();
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.config.ConfigEntityGadget;
import emu.grasscutter.data.excels.GadgetData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.PlayerProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.EntityAuthorityInfoOuterClass.EntityAuthorityInfo;
import emu.grasscutter.net.proto.EntityRendererChangedInfoOuterClass.EntityRendererChangedInfo;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.net.proto.VehicleInfoOuterClass.VehicleInfo;
import emu.grasscutter.net.proto.VehicleMemberOuterClass.VehicleMember;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.Setter;
public class EntityVehicle extends EntityBaseGadget {
@Getter private final Player owner;
@Getter(onMethod = @__(@Override))
private final Int2FloatMap fightProperties;
@Getter private final int pointId;
@Getter private final int gadgetId;
@Getter @Setter private float curStamina;
@Getter private final List<VehicleMember> vehicleMembers;
@Nullable @Getter private ConfigEntityGadget configGadget;
public EntityVehicle(
Scene scene, Player player, int gadgetId, int pointId, Position pos, Position rot) {
super(scene, pos, rot);
this.owner = player;
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
this.fightProperties = new Int2FloatOpenHashMap();
this.gadgetId = gadgetId;
this.pointId = pointId;
this.curStamina = 240; // might be in configGadget.GCALKECLLLP.JBAKBEFIMBN.ANBMPHPOALP
this.vehicleMembers = new ArrayList<>();
GadgetData data = GameData.getGadgetDataMap().get(gadgetId);
if (data != null && data.getJsonName() != null) {
this.configGadget = GameData.getGadgetConfigData().get(data.getJsonName());
}
fillFightProps(configGadget);
}
@Override
protected void fillFightProps(ConfigEntityGadget configGadget) {
super.fillFightProps(configGadget);
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_SPEED, 0);
this.addFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, 0);
}
@Override
public SceneEntityInfo toProto() {
VehicleInfo vehicle =
VehicleInfo.newBuilder()
.setOwnerUid(this.owner.getUid())
.setCurStamina(getCurStamina())
.build();
EntityAuthorityInfo authority =
EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfo.newBuilder()
.setIsAiOpen(true)
.setBornPos(getPosition().toProto()))
.setBornPos(getPosition().toProto())
.build();
SceneGadgetInfo.Builder gadgetInfo =
SceneGadgetInfo.newBuilder()
.setGadgetId(this.getGadgetId())
.setAuthorityPeerId(this.getOwner().getPeerId())
.setIsEnableInteract(true)
.setVehicleInfo(vehicle);
SceneEntityInfo.Builder entityInfo =
SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_GADGET)
.setMotionInfo(
MotionInfo.newBuilder()
.setPos(getPosition().toProto())
.setRot(getRotation().toProto())
.setSpeed(Vector.newBuilder()))
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setGadget(gadgetInfo)
.setEntityAuthorityInfo(authority)
.setLifeState(1);
PropPair pair =
PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, 47))
.build();
this.addAllFightPropsToEntityInfo(entityInfo);
entityInfo.addPropList(pair);
return entityInfo.build();
}
}

View File

@@ -1,37 +1,37 @@
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);
}
}
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,34 +1,34 @@
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.ResinCostTypeOuterClass;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
public final class GadgetRewardStatue extends GadgetContent {
public GadgetRewardStatue(EntityGadget gadget) {
super(gadget);
}
public boolean onInteract(Player player, GadgetInteractReq req) {
var dungeonManager = player.getScene().getDungeonManager();
if (player.getScene().getChallenge() instanceof DungeonChallenge) {
var useCondensed =
req.getResinCostType() == ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE;
dungeonManager.getStatueDrops(player, useCondensed, getGadget().getGroupId());
}
player.sendPacket(
new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_OPEN_STATUE));
return false;
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {}
}
package emu.grasscutter.game.entity.gadget;
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.InteractTypeOuterClass.InteractType;
import emu.grasscutter.net.proto.ResinCostTypeOuterClass;
import emu.grasscutter.net.proto.SceneGadgetInfoOuterClass.SceneGadgetInfo;
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
public final class GadgetRewardStatue extends GadgetContent {
public GadgetRewardStatue(EntityGadget gadget) {
super(gadget);
}
public boolean onInteract(Player player, GadgetInteractReq req) {
var dungeonManager = player.getScene().getDungeonManager();
if (player.getScene().getChallenge() instanceof DungeonChallenge) {
var useCondensed =
req.getResinCostType() == ResinCostTypeOuterClass.ResinCostType.RESIN_COST_TYPE_CONDENSE;
dungeonManager.getStatueDrops(player, useCondensed, getGadget().getGroupId());
}
player.sendPacket(
new PacketGadgetInteractRsp(getGadget(), InteractType.INTERACT_TYPE_OPEN_STATUE));
return false;
}
public void onBuildProto(SceneGadgetInfo.Builder gadgetInfo) {}
}

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,83 +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,31 +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

@@ -1,45 +1,45 @@
package emu.grasscutter.game.entity.platform;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.binout.config.ConfigEntityGadget;
import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.entity.gadget.GadgetAbility;
import emu.grasscutter.game.entity.gadget.platform.AbilityRoute;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.utils.Position;
public class EntitySolarIsotomaElevatorPlatform extends EntityGadget {
public EntitySolarIsotomaElevatorPlatform(
EntitySolarIsotomaClientGadget isotoma,
Scene scene,
int gadgetId,
Position pos,
Position rot) {
super(scene, gadgetId, pos, rot);
setOwner(isotoma);
this.setRouteConfig(new AbilityRoute(rot, false, false, pos));
this.setContent(new GadgetAbility(this, isotoma));
}
@Override
protected void fillFightProps(ConfigEntityGadget configGadget) {
if (configGadget == null || configGadget.getCombat() == null) {
return;
}
var combatData = configGadget.getCombat();
var combatProperties = combatData.getProperty();
if (combatProperties.isUseCreatorProperty()) {
// If useCreatorProperty == true, use owner's property;
GameEntity ownerEntity = getOwner();
if (ownerEntity != null) {
getFightProperties().putAll(ownerEntity.getFightProperties());
return;
} else {
Grasscutter.getLogger().warn("Why gadget owner is null?");
}
}
super.fillFightProps(configGadget);
}
}
package emu.grasscutter.game.entity.platform;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.binout.config.ConfigEntityGadget;
import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.entity.gadget.GadgetAbility;
import emu.grasscutter.game.entity.gadget.platform.AbilityRoute;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.utils.Position;
public class EntitySolarIsotomaElevatorPlatform extends EntityGadget {
public EntitySolarIsotomaElevatorPlatform(
EntitySolarIsotomaClientGadget isotoma,
Scene scene,
int gadgetId,
Position pos,
Position rot) {
super(scene, gadgetId, pos, rot);
setOwner(isotoma);
this.setRouteConfig(new AbilityRoute(rot, false, false, pos));
this.setContent(new GadgetAbility(this, isotoma));
}
@Override
protected void fillFightProps(ConfigEntityGadget configGadget) {
if (configGadget == null || configGadget.getCombat() == null) {
return;
}
var combatData = configGadget.getCombat();
var combatProperties = combatData.getProperty();
if (combatProperties.isUseCreatorProperty()) {
// If useCreatorProperty == true, use owner's property;
GameEntity ownerEntity = getOwner();
if (ownerEntity != null) {
getFightProperties().putAll(ownerEntity.getFightProperties());
return;
} else {
Grasscutter.getLogger().warn("Why gadget owner is null?");
}
}
super.fillFightProps(configGadget);
}
}

View File

@@ -1,173 +1,173 @@
package emu.grasscutter.game.mail;
import static emu.grasscutter.net.proto.MailItemOuterClass.MailItem.*;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.Transient;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.net.proto.EquipParamOuterClass.EquipParam;
import emu.grasscutter.net.proto.MailCollectStateOuterClass.MailCollectState;
import emu.grasscutter.net.proto.MailTextContentOuterClass.MailTextContent;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import org.bson.types.ObjectId;
@Entity(value = "mail", useDiscriminator = false)
public final class Mail {
@Id private ObjectId id;
@Indexed private int ownerUid;
public MailContent mailContent;
public List<MailItem> itemList;
public long sendTime;
public long expireTime;
public int importance;
public boolean isRead;
public boolean isAttachmentGot;
public int stateValue;
@Transient private boolean shouldDelete;
public Mail() {
this(
new MailContent(),
new ArrayList<MailItem>(),
(int) Instant.now().getEpochSecond()
+ 604800); // TODO: add expire time to send mail command
}
public Mail(MailContent mailContent, List<MailItem> itemList, long expireTime) {
this(mailContent, itemList, expireTime, 0);
}
public Mail(MailContent mailContent, List<MailItem> itemList, long expireTime, int importance) {
this(mailContent, itemList, expireTime, importance, 1);
}
public Mail(
MailContent mailContent,
List<MailItem> itemList,
long expireTime,
int importance,
int state) {
this.mailContent = mailContent;
this.itemList = itemList;
this.sendTime = (int) Instant.now().getEpochSecond();
this.expireTime = expireTime;
this.importance = importance; // Starred mail, 0 = No star, 1 = Star.
this.isRead = false;
this.isAttachmentGot = false;
this.stateValue = state; // Different mailboxes, 1 = Default, 3 = Gift-box.
}
public ObjectId getId() {
return id;
}
public int getOwnerUid() {
return ownerUid;
}
public void setOwnerUid(int ownerUid) {
this.ownerUid = ownerUid;
}
public MailDataOuterClass.MailData toProto(Player player) {
return MailDataOuterClass.MailData.newBuilder()
.setMailId(player.getMailId(this))
.setMailTextContent(this.mailContent.toProto())
.addAllItemList(this.itemList.stream().map(MailItem::toProto).toList())
.setSendTime((int) this.sendTime)
.setExpireTime((int) this.expireTime)
.setImportance(this.importance)
.setIsRead(this.isRead)
.setIsAttachmentGot(this.isAttachmentGot)
.setCollectState(MailCollectState.MAIL_COLLECT_STATE_NOT_COLLECTIBLE)
.build();
}
@Entity
public static class MailContent {
public String title;
public String content;
public String sender;
public MailContent() {
this.title = "";
this.content = "loading...";
this.sender = "loading";
}
public MailContent(String title, String content) {
this(title, content, "Server");
}
public MailContent(String title, String content, Player sender) {
this(title, content, sender.getNickname());
}
public MailContent(String title, String content, String sender) {
this.title = title;
this.content = content;
this.sender = sender;
}
public MailTextContent toProto() {
return MailTextContent.newBuilder()
.setTitle(this.title)
.setContent(this.content)
.setSender(this.sender)
.build();
}
}
@Entity
public static class MailItem {
public int itemId;
public int itemCount;
public int itemLevel;
public MailItem() {
this.itemId = 11101;
this.itemCount = 1;
this.itemLevel = 1;
}
public MailItem(int itemId) {
this(itemId, 1);
}
public MailItem(int itemId, int itemCount) {
this(itemId, itemCount, 1);
}
public MailItem(int itemId, int itemCount, int itemLevel) {
this.itemId = itemId;
this.itemCount = itemCount;
this.itemLevel = itemLevel;
}
public MailItemOuterClass.MailItem toProto() {
return newBuilder()
.setEquipParam(
EquipParam.newBuilder()
.setItemId(this.itemId)
.setItemNum(this.itemCount)
.setItemLevel(this.itemLevel)
.setPromoteLevel(0) // mock
.build())
.build();
}
}
public void save() {
if (this.expireTime * 1000 < System.currentTimeMillis()) {
DatabaseHelper.deleteMail(this);
} else {
DatabaseHelper.saveMail(this);
}
}
}
package emu.grasscutter.game.mail;
import static emu.grasscutter.net.proto.MailItemOuterClass.MailItem.*;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import dev.morphia.annotations.Transient;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.net.proto.EquipParamOuterClass.EquipParam;
import emu.grasscutter.net.proto.MailCollectStateOuterClass.MailCollectState;
import emu.grasscutter.net.proto.MailTextContentOuterClass.MailTextContent;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import org.bson.types.ObjectId;
@Entity(value = "mail", useDiscriminator = false)
public final class Mail {
@Id private ObjectId id;
@Indexed private int ownerUid;
public MailContent mailContent;
public List<MailItem> itemList;
public long sendTime;
public long expireTime;
public int importance;
public boolean isRead;
public boolean isAttachmentGot;
public int stateValue;
@Transient private boolean shouldDelete;
public Mail() {
this(
new MailContent(),
new ArrayList<MailItem>(),
(int) Instant.now().getEpochSecond()
+ 604800); // TODO: add expire time to send mail command
}
public Mail(MailContent mailContent, List<MailItem> itemList, long expireTime) {
this(mailContent, itemList, expireTime, 0);
}
public Mail(MailContent mailContent, List<MailItem> itemList, long expireTime, int importance) {
this(mailContent, itemList, expireTime, importance, 1);
}
public Mail(
MailContent mailContent,
List<MailItem> itemList,
long expireTime,
int importance,
int state) {
this.mailContent = mailContent;
this.itemList = itemList;
this.sendTime = (int) Instant.now().getEpochSecond();
this.expireTime = expireTime;
this.importance = importance; // Starred mail, 0 = No star, 1 = Star.
this.isRead = false;
this.isAttachmentGot = false;
this.stateValue = state; // Different mailboxes, 1 = Default, 3 = Gift-box.
}
public ObjectId getId() {
return id;
}
public int getOwnerUid() {
return ownerUid;
}
public void setOwnerUid(int ownerUid) {
this.ownerUid = ownerUid;
}
public MailDataOuterClass.MailData toProto(Player player) {
return MailDataOuterClass.MailData.newBuilder()
.setMailId(player.getMailId(this))
.setMailTextContent(this.mailContent.toProto())
.addAllItemList(this.itemList.stream().map(MailItem::toProto).toList())
.setSendTime((int) this.sendTime)
.setExpireTime((int) this.expireTime)
.setImportance(this.importance)
.setIsRead(this.isRead)
.setIsAttachmentGot(this.isAttachmentGot)
.setCollectState(MailCollectState.MAIL_COLLECT_STATE_NOT_COLLECTIBLE)
.build();
}
@Entity
public static class MailContent {
public String title;
public String content;
public String sender;
public MailContent() {
this.title = "";
this.content = "loading...";
this.sender = "loading";
}
public MailContent(String title, String content) {
this(title, content, "Server");
}
public MailContent(String title, String content, Player sender) {
this(title, content, sender.getNickname());
}
public MailContent(String title, String content, String sender) {
this.title = title;
this.content = content;
this.sender = sender;
}
public MailTextContent toProto() {
return MailTextContent.newBuilder()
.setTitle(this.title)
.setContent(this.content)
.setSender(this.sender)
.build();
}
}
@Entity
public static class MailItem {
public int itemId;
public int itemCount;
public int itemLevel;
public MailItem() {
this.itemId = 11101;
this.itemCount = 1;
this.itemLevel = 1;
}
public MailItem(int itemId) {
this(itemId, 1);
}
public MailItem(int itemId, int itemCount) {
this(itemId, itemCount, 1);
}
public MailItem(int itemId, int itemCount, int itemLevel) {
this.itemId = itemId;
this.itemCount = itemCount;
this.itemLevel = itemLevel;
}
public MailItemOuterClass.MailItem toProto() {
return newBuilder()
.setEquipParam(
EquipParam.newBuilder()
.setItemId(this.itemId)
.setItemNum(this.itemCount)
.setItemLevel(this.itemLevel)
.setPromoteLevel(0) // mock
.build())
.build();
}
}
public void save() {
if (this.expireTime * 1000 < System.currentTimeMillis()) {
DatabaseHelper.deleteMail(this);
} else {
DatabaseHelper.saveMail(this);
}
}
}

View File

@@ -1,146 +1,146 @@
package emu.grasscutter.game.managers.blossom;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneBossChest;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
public final class BlossomActivity {
private final SceneGroup tempSceneGroup;
private final WorldChallenge challenge;
private final EntityGadget gadget;
private EntityGadget chest;
private int step;
private final int goal;
private int generatedCount;
private final int worldLevel;
private boolean pass = false;
private final List<EntityMonster> activeMonsters = new ArrayList<>();
private final Queue<Integer> candidateMonsters = new ArrayDeque<>();
private static final int BLOOMING_GADGET_ID = 70210109;
public BlossomActivity(
EntityGadget entityGadget, List<Integer> monsters, int timeout, int worldLevel) {
this.tempSceneGroup = new SceneGroup();
this.tempSceneGroup.id = entityGadget.getId();
this.gadget = entityGadget;
this.step = 0;
this.goal = monsters.size();
this.candidateMonsters.addAll(monsters);
this.worldLevel = worldLevel;
ArrayList<ChallengeTrigger> challengeTriggers = new ArrayList<>();
this.challenge =
new WorldChallenge(
entityGadget.getScene(),
tempSceneGroup,
1,
1,
List.of(goal, timeout),
timeout,
goal,
challengeTriggers);
challengeTriggers.add(new KillMonsterCountTrigger());
// this.challengeTriggers.add(new InTimeTrigger());
}
public WorldChallenge getChallenge() {
return this.challenge;
}
public void setMonsters(List<EntityMonster> monsters) {
this.activeMonsters.clear();
this.activeMonsters.addAll(monsters);
for (EntityMonster monster : monsters) {
monster.setGroupId(this.tempSceneGroup.id);
}
}
public int getAliveMonstersCount() {
int count = 0;
for (EntityMonster monster : activeMonsters) {
if (monster.isAlive()) {
count++;
}
}
return count;
}
public boolean getPass() {
return pass;
}
public void start() {
challenge.start();
}
public void onTick() {
Scene scene = gadget.getScene();
Position pos = gadget.getPosition();
if (getAliveMonstersCount() <= 2) {
if (generatedCount < goal) {
step++;
var worldLevelData = GameData.getWorldLevelDataMap().get(worldLevel);
int worldLevelOverride = 0;
if (worldLevelData != null) {
worldLevelOverride = worldLevelData.getMonsterLevel();
}
List<EntityMonster> newMonsters = new ArrayList<>();
int willSpawn = Utils.randomRange(3, 5);
if (generatedCount + willSpawn > goal) {
willSpawn = goal - generatedCount;
}
generatedCount += willSpawn;
for (int i = 0; i < willSpawn; i++) {
var monsterData = GameData.getMonsterDataMap().get(candidateMonsters.poll());
int level = scene.getEntityLevel(1, worldLevelOverride);
EntityMonster entity = new EntityMonster(scene, monsterData, pos.nearby2d(4f), level);
scene.addEntity(entity);
newMonsters.add(entity);
}
setMonsters(newMonsters);
} else {
if (getAliveMonstersCount() == 0) {
this.pass = true;
this.challenge.done();
}
}
}
}
public EntityGadget getGadget() {
return gadget;
}
public EntityGadget getChest() {
if (chest == null) {
EntityGadget rewardGadget =
new EntityGadget(gadget.getScene(), BLOOMING_GADGET_ID, gadget.getPosition());
SceneGadget metaGadget = new SceneGadget();
metaGadget.boss_chest = new SceneBossChest();
metaGadget.boss_chest.resin = 20;
rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, Float.POSITIVE_INFINITY);
rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, Float.POSITIVE_INFINITY);
rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, Float.POSITIVE_INFINITY);
rewardGadget.setMetaGadget(metaGadget);
rewardGadget.buildContent();
chest = rewardGadget;
}
return chest;
}
}
package emu.grasscutter.game.managers.blossom;
import emu.grasscutter.data.GameData;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.data.SceneBossChest;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.Utils;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
public final class BlossomActivity {
private final SceneGroup tempSceneGroup;
private final WorldChallenge challenge;
private final EntityGadget gadget;
private EntityGadget chest;
private int step;
private final int goal;
private int generatedCount;
private final int worldLevel;
private boolean pass = false;
private final List<EntityMonster> activeMonsters = new ArrayList<>();
private final Queue<Integer> candidateMonsters = new ArrayDeque<>();
private static final int BLOOMING_GADGET_ID = 70210109;
public BlossomActivity(
EntityGadget entityGadget, List<Integer> monsters, int timeout, int worldLevel) {
this.tempSceneGroup = new SceneGroup();
this.tempSceneGroup.id = entityGadget.getId();
this.gadget = entityGadget;
this.step = 0;
this.goal = monsters.size();
this.candidateMonsters.addAll(monsters);
this.worldLevel = worldLevel;
ArrayList<ChallengeTrigger> challengeTriggers = new ArrayList<>();
this.challenge =
new WorldChallenge(
entityGadget.getScene(),
tempSceneGroup,
1,
1,
List.of(goal, timeout),
timeout,
goal,
challengeTriggers);
challengeTriggers.add(new KillMonsterCountTrigger());
// this.challengeTriggers.add(new InTimeTrigger());
}
public WorldChallenge getChallenge() {
return this.challenge;
}
public void setMonsters(List<EntityMonster> monsters) {
this.activeMonsters.clear();
this.activeMonsters.addAll(monsters);
for (EntityMonster monster : monsters) {
monster.setGroupId(this.tempSceneGroup.id);
}
}
public int getAliveMonstersCount() {
int count = 0;
for (EntityMonster monster : activeMonsters) {
if (monster.isAlive()) {
count++;
}
}
return count;
}
public boolean getPass() {
return pass;
}
public void start() {
challenge.start();
}
public void onTick() {
Scene scene = gadget.getScene();
Position pos = gadget.getPosition();
if (getAliveMonstersCount() <= 2) {
if (generatedCount < goal) {
step++;
var worldLevelData = GameData.getWorldLevelDataMap().get(worldLevel);
int worldLevelOverride = 0;
if (worldLevelData != null) {
worldLevelOverride = worldLevelData.getMonsterLevel();
}
List<EntityMonster> newMonsters = new ArrayList<>();
int willSpawn = Utils.randomRange(3, 5);
if (generatedCount + willSpawn > goal) {
willSpawn = goal - generatedCount;
}
generatedCount += willSpawn;
for (int i = 0; i < willSpawn; i++) {
var monsterData = GameData.getMonsterDataMap().get(candidateMonsters.poll());
int level = scene.getEntityLevel(1, worldLevelOverride);
EntityMonster entity = new EntityMonster(scene, monsterData, pos.nearby2d(4f), level);
scene.addEntity(entity);
newMonsters.add(entity);
}
setMonsters(newMonsters);
} else {
if (getAliveMonstersCount() == 0) {
this.pass = true;
this.challenge.done();
}
}
}
}
public EntityGadget getGadget() {
return gadget;
}
public EntityGadget getChest() {
if (chest == null) {
EntityGadget rewardGadget =
new EntityGadget(gadget.getScene(), BLOOMING_GADGET_ID, gadget.getPosition());
SceneGadget metaGadget = new SceneGadget();
metaGadget.boss_chest = new SceneBossChest();
metaGadget.boss_chest.resin = 20;
rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, Float.POSITIVE_INFINITY);
rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, Float.POSITIVE_INFINITY);
rewardGadget.setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, Float.POSITIVE_INFINITY);
rewardGadget.setMetaGadget(metaGadget);
rewardGadget.buildContent();
chest = rewardGadget;
}
return chest;
}
}

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;
@@ -15,6 +15,7 @@ import emu.grasscutter.game.activity.ActivityManager;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.avatar.AvatarStorage;
import emu.grasscutter.game.battlepass.BattlePassManager;
import emu.grasscutter.game.entity.EntityAvatar;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.expedition.ExpeditionInfo;
import emu.grasscutter.game.friends.FriendsList;
@@ -44,27 +45,26 @@ 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.QuestTrigger;
import emu.grasscutter.game.quest.enums.QuestCond;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.shop.ShopLimit;
import emu.grasscutter.game.tower.TowerData;
import emu.grasscutter.game.tower.TowerManager;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.World;
import emu.grasscutter.net.packet.BasePacket;
import emu.grasscutter.net.proto.*;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.CombatInvokeEntryOuterClass.CombatInvokeEntry;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.MpSettingTypeOuterClass.MpSettingType;
import emu.grasscutter.net.proto.OnlinePlayerInfoOuterClass.OnlinePlayerInfo;
import emu.grasscutter.net.proto.PlayerApplyEnterMpResultNotifyOuterClass;
import emu.grasscutter.net.proto.PlayerLocationInfoOuterClass.PlayerLocationInfo;
import emu.grasscutter.net.proto.PlayerWorldLocationInfoOuterClass;
import emu.grasscutter.net.proto.ProfilePictureOuterClass.ProfilePicture;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.net.proto.ShowAvatarInfoOuterClass;
import emu.grasscutter.net.proto.SocialDetailOuterClass.SocialDetail;
import emu.grasscutter.net.proto.SocialShowAvatarInfoOuterClass;
import emu.grasscutter.net.proto.TrialAvatarGrantRecordOuterClass.TrialAvatarGrantRecord.GrantReason;
import emu.grasscutter.scripts.data.SceneRegion;
import emu.grasscutter.server.event.player.PlayerJoinEvent;
import emu.grasscutter.server.event.player.PlayerQuitEvent;
@@ -88,6 +88,7 @@ import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.stream.Stream;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
@@ -116,12 +117,13 @@ public class Player {
@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 @Setter private boolean inGodMode;
@Getter @Setter private boolean unlimitedStamina;
@Getter private Set<Integer> nameCardList;
@Getter private Set<Integer> flyCloakList;
@Getter private Set<Integer> costumeList;
@Getter private Set<Integer> personalLineList;
@Getter @Setter private Set<Integer> rewardedLevels;
@Getter @Setter private Set<Integer> homeRewardedLevels;
@Getter @Setter private Set<Integer> realmList;
@@ -205,7 +207,11 @@ public class Player {
@Getter @Setter private int nextResinRefresh;
@Getter @Setter private int resinBuyCount;
@Getter @Setter private int lastDailyReset;
@Getter private transient MpSettingType mpSetting = MpSettingType.MP_SETTING_TYPE_ENTER_AFTER_APPLY; // TODO
@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!
@@ -242,7 +248,7 @@ public class Player {
this.unlockedCombines = new HashSet<>();
this.unlockedFurniture = new HashSet<>();
this.unlockedFurnitureSuite = new HashSet<>();
this.activeCookCompounds=new HashMap<>();
this.activeCookCompounds = new HashMap<>();
this.activeForges = new ArrayList<>();
this.unlockedRecipies = new HashMap<>();
this.questGlobalVariables = new HashMap<>();
@@ -250,6 +256,8 @@ public class Player {
this.unlockedSceneAreas = new HashMap<>();
this.unlockedScenePoints = new HashMap<>();
this.chatEmojiIdList = new ArrayList<>();
this.playerProgress = new PlayerProgress();
this.activeQuestTimers = new HashSet<>();
this.attackResults = new LinkedBlockingQueue<>();
this.coopRequests = new Int2ObjectOpenHashMap<>();
@@ -276,7 +284,7 @@ public class Player {
this.progressManager = new PlayerProgressManager(this);
this.furnitureManager = new FurnitureManager(this);
this.cookingManager = new CookingManager(this);
this.cookingCompoundManager=new CookingCompoundManager(this);
this.cookingCompoundManager = new CookingCompoundManager(this);
this.satiationManager = new SatiationManager(this);
}
@@ -312,10 +320,22 @@ public class Player {
this.progressManager = new PlayerProgressManager(this);
this.furnitureManager = new FurnitureManager(this);
this.cookingManager = new CookingManager(this);
this.cookingCompoundManager=new CookingCompoundManager(this);
this.cookingCompoundManager = new CookingCompoundManager(this);
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;
}
@@ -456,6 +476,8 @@ public class Player {
// Handle open state unlocks from level-up.
this.getProgressManager().tryUnlockOpenStates();
this.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_PLAYER_LEVEL_UP, level);
this.getQuestManager().queueEvent(QuestCond.QUEST_COND_PLAYER_LEVEL_EQUAL_GREATER, level);
return true;
}
@@ -523,6 +545,7 @@ public class Player {
public boolean setHomeCoin(int coin) {
return this.setProperty(PlayerProperty.PROP_PLAYER_HOME_COIN, coin);
}
private int getExpRequired(int level) {
PlayerLevelData levelData = GameData.getPlayerLevelDataMap().get(level);
return levelData != null ? levelData.getExp() : 0;
@@ -564,14 +587,14 @@ public class Player {
int newWorldLevel =
(currentLevel >= 55) ? 8 :
(currentLevel >= 50) ? 7 :
(currentLevel >= 45) ? 6 :
(currentLevel >= 40) ? 5 :
(currentLevel >= 35) ? 4 :
(currentLevel >= 30) ? 3 :
(currentLevel >= 25) ? 2 :
(currentLevel >= 20) ? 1 :
0;
(currentLevel >= 50) ? 7 :
(currentLevel >= 45) ? 6 :
(currentLevel >= 40) ? 5 :
(currentLevel >= 35) ? 4 :
(currentLevel >= 30) ? 3 :
(currentLevel >= 25) ? 2 :
(currentLevel >= 20) ? 1 :
0;
if (newWorldLevel != currentWorldLevel) {
this.setWorldLevel(newWorldLevel);
@@ -596,11 +619,12 @@ public class Player {
public void onEnterRegion(SceneRegion region) {
getQuestManager().forEachActiveQuest(quest -> {
if (quest.getTriggers().containsKey("ENTER_REGION_"+ region.config_id)) {
if (quest.getTriggerData() != null && quest.getTriggers().containsKey("ENTER_REGION_"+ region.config_id)) {
// If trigger hasn't been fired yet
if (!Boolean.TRUE.equals(quest.getTriggers().put("ENTER_REGION_"+ region.config_id, true))) {
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);
}
}
});
@@ -609,11 +633,12 @@ public class Player {
public void onLeaveRegion(SceneRegion region) {
getQuestManager().forEachActiveQuest(quest -> {
if (quest.getTriggers().containsKey("LEAVE_REGION_"+ region.config_id)) {
if (quest.getTriggers().containsKey("LEAVE_REGION_" + region.config_id)) {
// If trigger hasn't been fired yet
if (!Boolean.TRUE.equals(quest.getTriggers().put("LEAVE_REGION_"+ region.config_id, true))) {
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);
}
}
});
@@ -705,9 +730,7 @@ public class Player {
} else {
moonCardDuration += 30;
}
if (!moonCardGetTimes.contains(moonCardStartTime)) {
moonCardGetTimes.add(moonCardStartTime);
}
moonCardGetTimes.add(moonCardStartTime);
return true;
}
@@ -776,18 +799,6 @@ public class Player {
this.save();
}
public boolean getUnlimitedStamina() {
return stamina;
}
public void setUnlimitedStamina(boolean stamina) {
this.stamina = stamina;
}
public boolean inGodmode() {
return godmode;
}
public boolean hasSentLoginPackets() {
return hasSentLoginPackets;
}
@@ -819,6 +830,85 @@ public class Player {
addAvatar(avatar, true);
}
public void addAvatar(int avatarId) {
// I dont see why we cant do this lolz
addAvatar(new Avatar(avatarId), true);
}
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();
var trialCustomParams = GameData.getTrialAvatarCustomData().get(trialAvatarId).getTrialAvatarParamList();
return trialCustomParams.isEmpty() ? List.of() : Stream.of(trialCustomParams.get(0).split(";")).map(Integer::parseInt).toList();
}
public boolean addTrialAvatar(int trialAvatarId, GrantReason reason, int questMainId){
List<Integer> trialAvatarBasicParam = getTrialAvatarParam(trialAvatarId);
if (trialAvatarBasicParam.isEmpty()) return false;
Avatar avatar = new Avatar(trialAvatarBasicParam.get(0));
if (avatar.getAvatarData() == null || !hasSentLoginPackets()) return false;
avatar.setOwner(this);
// Add trial weapons and relics
avatar.setTrialAvatarInfo(trialAvatarBasicParam.get(1), trialAvatarId, reason, questMainId);
avatar.equipTrialItems();
// Recalc stats
avatar.recalcStats();
// Packet, mimic official server behaviour, add to player's bag but not saving to db
sendPacket(new PacketAvatarAddNotify(avatar, false));
// add to avatar to temporary trial team
getTeamManager().addAvatarToTrialTeam(avatar);
return true;
}
public boolean addTrialAvatarForQuest(int trialAvatarId, int questMainId) {
// TODO: Find method for 'setupTrialAvatarTeamForQuest'.
getTeamManager().setupTrialAvatars(true);
if (!addTrialAvatar(
trialAvatarId,
GrantReason.GRANT_REASON_BY_QUEST,
questMainId)) return false;
getTeamManager().trialAvatarTeamPostUpdate();
// Packet, mimic official server behaviour, neccessary to stop player from modifying team
sendPacket(new PacketAvatarTeamUpdateNotify(this));
return true;
}
public void addTrialAvatarsForActivity(List<Integer> trialAvatarIds) {
getTeamManager().setupTrialAvatars(false);
trialAvatarIds.forEach(trialAvatarId -> addTrialAvatar(
trialAvatarId,
GrantReason.GRANT_REASON_BY_TRIAL_AVATAR_ACTIVITY,
0));
getTeamManager().trialAvatarTeamPostUpdate(0);
}
public boolean removeTrialAvatarForQuest(int trialAvatarId) {
if (!getTeamManager().isUsingTrialTeam()) return false;
sendPacket(new PacketAvatarDelNotify(List.of(getTeamManager().getTrialAvatarGuid(trialAvatarId))));
getTeamManager().removeTrialAvatarTeam(trialAvatarId);
sendPacket(new PacketAvatarTeamUpdateNotify());
return true;
}
public void removeTrialAvatarForActivity() {
if (!getTeamManager().isUsingTrialTeam()) return;
sendPacket(new PacketAvatarDelNotify(getTeamManager().getActiveTeam().stream()
.map(x -> x.getAvatar().getGuid()).toList()));
getTeamManager().removeTrialAvatarTeam();
}
public void addFlycloak(int flycloakId) {
this.getFlyCloakList().add(flycloakId);
this.sendPacket(new PacketAvatarGainFlycloakNotify(flycloakId));
@@ -829,6 +919,11 @@ public class Player {
this.sendPacket(new PacketAvatarGainCostumeNotify(costumeId));
}
public void addPersonalLine(int personalLineId) {
this.getPersonalLineList().add(personalLineId);
session.getPlayer().getQuestManager().queueEvent(QuestCond.QUEST_COND_PERSONAL_LINE_UNLOCK, personalLineId);
}
public void addNameCard(int nameCardId) {
this.getNameCardList().add(nameCardId);
this.sendPacket(new PacketUnlockNameCardNotify(nameCardId));
@@ -844,6 +939,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());
@@ -853,6 +953,46 @@ public class Player {
this.getServer().getChatSystem().sendPrivateMessageFromServer(getUid(), message.toString());
}
public void setAvatarsAbilityForScene(Scene scene) {
try {
var levelEntityConfig = scene.getSceneData().getLevelEntityConfig();
var config = GameData.getConfigLevelEntityDataMap().get(levelEntityConfig);
if (config == null){
return;
}
List<Integer> avatarIds = scene.getSceneData().getSpecifiedAvatarList();
List<EntityAvatar> specifiedAvatarList = getTeamManager().getActiveTeam();
if (avatarIds != null && avatarIds.size() > 0){
// certain scene could limit specifc avatars' entry
specifiedAvatarList.clear();
for (int id : avatarIds){
var avatar = getAvatars().getAvatarById(id);
if (avatar == null){
continue;
}
specifiedAvatarList.add(new EntityAvatar(scene, avatar));
}
}
for (EntityAvatar entityAvatar : specifiedAvatarList){
var avatarData = entityAvatar.getAvatar().getAvatarData();
if (avatarData == null){
continue;
}
avatarData.buildEmbryo();
if (config.getAvatarAbilities() == null){
continue; // continue and not break because has to rebuild ability for the next avatar if any
}
for (var 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);
}
}
/**
* Sends a message to another player.
*
@@ -865,7 +1005,9 @@ public class Player {
// ---------------------MAIL------------------------
public List<Mail> getAllMail() { return this.getMailHandler().getMail(); }
public List<Mail> getAllMail() {
return this.getMailHandler().getMail();
}
public void sendMail(Mail message) {
this.getMailHandler().sendMail(message);
@@ -875,7 +1017,9 @@ public class Player {
return this.getMailHandler().deleteMail(mailId);
}
public Mail getMail(int index) { return this.getMailHandler().getMailById(index); }
public Mail getMail(int index) {
return this.getMailHandler().getMailById(index);
}
public int getMailId(Mail message) {
return this.getMailHandler().getMailIndex(message);
@@ -909,13 +1053,13 @@ public class Player {
public OnlinePlayerInfo getOnlinePlayerInfo() {
OnlinePlayerInfo.Builder onlineInfo = OnlinePlayerInfo.newBuilder()
.setUid(this.getUid())
.setNickname(this.getNickname())
.setPlayerLevel(this.getLevel())
.setMpSettingType(this.getMpSetting())
.setNameCardId(this.getNameCardId())
.setSignature(this.getSignature())
.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(this.getHeadImage()));
.setUid(this.getUid())
.setNickname(this.getNickname())
.setPlayerLevel(this.getLevel())
.setMpSettingType(this.getMpSetting())
.setNameCardId(this.getNameCardId())
.setSignature(this.getSignature())
.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(this.getHeadImage()));
if (this.getWorld() != null) {
onlineInfo.setCurPlayerNumInWorld(getWorld().getPlayerCount());
@@ -941,12 +1085,12 @@ public class Player {
if (this.getShowAvatarList() != null) {
for (int avatarId : this.getShowAvatarList()) {
socialShowAvatarInfoList.add(
socialShowAvatarInfoList.size(),
SocialShowAvatarInfoOuterClass.SocialShowAvatarInfo.newBuilder()
.setAvatarId(avatarId)
.setLevel(getAvatars().getAvatarById(avatarId).getLevel())
.setCostumeId(getAvatars().getAvatarById(avatarId).getCostume())
.build()
socialShowAvatarInfoList.size(),
SocialShowAvatarInfoOuterClass.SocialShowAvatarInfo.newBuilder()
.setAvatarId(avatarId)
.setLevel(getAvatars().getAvatarById(avatarId).getLevel())
.setCostumeId(getAvatars().getAvatarById(avatarId).getCostume())
.build()
);
}
}
@@ -957,32 +1101,31 @@ public class Player {
if (showAvatarList != null) {
for (int avatarId : showAvatarList) {
socialShowAvatarInfoList.add(
socialShowAvatarInfoList.size(),
SocialShowAvatarInfoOuterClass.SocialShowAvatarInfo.newBuilder()
.setAvatarId(avatarId)
.setLevel(avatars.getAvatarById(avatarId).getLevel())
.setCostumeId(avatars.getAvatarById(avatarId).getCostume())
.build()
socialShowAvatarInfoList.size(),
SocialShowAvatarInfoOuterClass.SocialShowAvatarInfo.newBuilder()
.setAvatarId(avatarId)
.setLevel(avatars.getAvatarById(avatarId).getLevel())
.setCostumeId(avatars.getAvatarById(avatarId).getCostume())
.build()
);
}
}
}
SocialDetail.Builder social = SocialDetail.newBuilder()
.setUid(this.getUid())
.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(this.getHeadImage()))
.setNickname(this.getNickname())
.setSignature(this.getSignature())
.setLevel(this.getLevel())
.setBirthday(this.getBirthday().getFilledProtoWhenNotEmpty())
.setWorldLevel(this.getWorldLevel())
.setNameCardId(this.getNameCardId())
.setIsShowAvatar(this.isShowAvatars())
.addAllShowAvatarInfoList(socialShowAvatarInfoList)
.addAllShowNameCardIdList(this.getShowNameCardInfoList())
.setFinishAchievementNum(this.getFinishedAchievementNum())
.setFriendEnterHomeOptionValue(this.getHome() == null ? 0 : this.getHome().getEnterHomeOption());
return social;
return SocialDetail.newBuilder()
.setUid(this.getUid())
.setProfilePicture(ProfilePicture.newBuilder().setAvatarId(this.getHeadImage()))
.setNickname(this.getNickname())
.setSignature(this.getSignature())
.setLevel(this.getLevel())
.setBirthday(this.getBirthday().getFilledProtoWhenNotEmpty())
.setWorldLevel(this.getWorldLevel())
.setNameCardId(this.getNameCardId())
.setIsShowAvatar(this.isShowAvatars())
.addAllShowAvatarInfoList(socialShowAvatarInfoList)
.addAllShowNameCardIdList(this.getShowNameCardInfoList())
.setFinishAchievementNum(this.getFinishedAchievementNum())
.setFriendEnterHomeOptionValue(this.getHome() == null ? 0 : this.getHome().getEnterHomeOption());
}
public int getFinishedAchievementNum() {
@@ -1025,17 +1168,17 @@ public class Player {
public PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo getWorldPlayerLocationInfo() {
return PlayerWorldLocationInfoOuterClass.PlayerWorldLocationInfo.newBuilder()
.setSceneId(this.getSceneId())
.setPlayerLoc(this.getPlayerLocationInfo())
.build();
.setSceneId(this.getSceneId())
.setPlayerLoc(this.getPlayerLocationInfo())
.build();
}
public PlayerLocationInfo getPlayerLocationInfo() {
return PlayerLocationInfo.newBuilder()
.setUid(this.getUid())
.setPos(this.getPosition().toProto())
.setRot(this.getRotation().toProto())
.build();
.setUid(this.getUid())
.setPos(this.getPosition().toProto())
.setRot(this.getRotation().toProto())
.build();
}
public void loadBattlePassManager() {
@@ -1045,7 +1188,7 @@ public class Player {
}
public PlayerCollectionRecords getCollectionRecordStore() {
if (this.collectionRecordStore==null) {
if (this.collectionRecordStore == null) {
this.collectionRecordStore = new PlayerCollectionRecords();
}
return collectionRecordStore;
@@ -1121,6 +1264,8 @@ public class Player {
// Home resources
this.getHome().updateHourlyResources(this);
this.getQuestManager().onTick();
}
private synchronized void doDailyReset() {
@@ -1189,12 +1334,17 @@ public class Player {
this.achievements = Achievements.getByPlayer(this);
this.getAvatars().loadFromDatabase();
this.getInventory().loadFromDatabase();
this.loadBattlePassManager(); // Call before avatar postLoad to avoid null pointer
this.getAvatars().postLoad(); // Needs to be called after inventory is handled
this.getFriendsList().loadFromDatabase();
this.getMailHandler().loadFromDatabase();
this.getQuestManager().loadFromDatabase();
this.loadBattlePassManager();
this.getAvatars().postLoad(); // Needs to be called after inventory is handled
}
public void onPlayerBorn() {
getQuestManager().onPlayerBorn();
}
public void onLogin() {
@@ -1274,7 +1424,8 @@ public class Player {
session.setState(SessionState.ACTIVE);
// Call join event.
PlayerJoinEvent event = new PlayerJoinEvent(this); event.call();
PlayerJoinEvent event = new PlayerJoinEvent(this);
event.call();
if (event.isCanceled()) { // If event is not cancelled, continue.
session.close();
return;
@@ -1313,11 +1464,12 @@ public class Player {
this.getFriendsList().save();
// Call quit event.
PlayerQuitEvent event = new PlayerQuitEvent(this); event.call();
}catch (Throwable e) {
PlayerQuitEvent event = new PlayerQuitEvent(this);
event.call();
} catch (Throwable e) {
e.printStackTrace();
Grasscutter.getLogger().warn("Player (UID {}) save failure", getUid());
}finally {
} finally {
removeFromServer();
}
}
@@ -1332,23 +1484,15 @@ public class Player {
public int getLegendaryKey() {
return this.getProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY);
}
public synchronized void addLegendaryKey(int count) {
this.setProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY, getLegendaryKey() + count);
}
public synchronized void useLegendaryKey(int count) {
this.setProperty(PlayerProperty.PROP_PLAYER_LEGENDARY_KEY, getLegendaryKey() - count);
}
public enum SceneLoadState {
NONE(0), LOADING(1), INIT(2), LOADED(3);
@Getter private final int value;
SceneLoadState(int value) {
this.value = value;
}
}
public int getPropertyMin(PlayerProperty prop) {
if (prop.isDynamicRange()) {
return 0;
@@ -1391,4 +1535,15 @@ public class Player {
}
}
public enum SceneLoadState {
NONE(0), LOADING(1), INIT(2), LOADED(3);
@Getter
private final int value;
SceneLoadState(int value) {
this.value = value;
}
}
}

View File

@@ -1,222 +1,222 @@
package emu.grasscutter.game.player;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.data.excels.BuffData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.proto.ServerBuffChangeNotifyOuterClass.ServerBuffChangeNotify.ServerBuffChangeType;
import emu.grasscutter.net.proto.ServerBuffOuterClass.ServerBuff;
import emu.grasscutter.server.packet.send.PacketServerBuffChangeNotify;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import lombok.Getter;
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<>();
}
/**
* Gets a new uid for a server buff
*
* @return New integer buff uid
*/
private int getNextBuffUid() {
return ++nextBuffUid;
}
/**
* Returns true if the player has a buff with this group id
*
* @param groupId Buff group id
* @return True if a buff with this group id exists
*/
public synchronized boolean hasBuff(int groupId) {
return this.buffs.containsKey(groupId);
}
/** Clears all player buffs */
public synchronized void clearBuffs() {
// Remove from player
getPlayer()
.sendPacket(
new PacketServerBuffChangeNotify(
getPlayer(),
ServerBuffChangeType.SERVER_BUFF_CHANGE_TYPE_DEL_SERVER_BUFF,
this.buffs.values()));
// Clear
this.buffs.clear();
}
/**
* Adds a server buff to the player.
*
* @param buffId Server buff id
* @return True if a buff was added
*/
public boolean addBuff(int buffId) {
return addBuff(buffId, -1f);
}
/**
* Adds a server buff to the player.
*
* @param buffId Server buff id
* @param duration Duration of the buff in seconds. Set to 0 for an infinite buff.
* @return True if a buff was added
*/
public synchronized boolean addBuff(int buffId, float duration) {
return addBuff(buffId, duration, null);
}
/**
* Adds a server buff to the player.
*
* @param buffId Server buff id
* @param duration Duration of the buff in seconds. Set to 0 for an infinite buff.
* @param target Target avatar
* @return True if a buff was added
*/
public synchronized boolean addBuff(int buffId, float duration, Avatar target) {
// Get buff excel data
var buffData = GameData.getBuffDataMap().get(buffId);
if (buffData == null) return false;
// Perform onAdded actions
var success =
Optional.ofNullable(GameData.getAbilityData(buffData.getAbilityName()))
.map(data -> data.modifiers.get(buffData.getModifierName()))
.map(modifier -> modifier.onAdded)
.map(
onAdded -> {
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 =
ability.amount.get() + ability.amountByTargetMaxHPRatio.get() * maxHp;
target.getAsEntity().heal(amount);
shouldHeal = true;
}
}
return shouldHeal;
})
.orElse(false);
// Set duration
if (duration < 0f) {
duration = buffData.getTime();
}
// Don't add buff if duration is equal or less than 0
if (duration <= 0) {
return success;
}
// Clear previous buff if it exists
this.removeBuff(buffData.getGroupId());
// Create and store buff
PlayerBuff buff = new PlayerBuff(getNextBuffUid(), buffData, duration);
this.buffs.put(buff.getGroupId(), buff);
// Packet
getPlayer()
.sendPacket(
new PacketServerBuffChangeNotify(
getPlayer(), ServerBuffChangeType.SERVER_BUFF_CHANGE_TYPE_ADD_SERVER_BUFF, buff));
return true;
}
/**
* Removes a buff by its group id
*
* @param buffGroupId Server buff group id
* @return True if a buff was remove
*/
public synchronized boolean removeBuff(int buffGroupId) {
PlayerBuff buff = this.buffs.remove(buffGroupId);
if (buff != null) {
getPlayer()
.sendPacket(
new PacketServerBuffChangeNotify(
getPlayer(), ServerBuffChangeType.SERVER_BUFF_CHANGE_TYPE_DEL_SERVER_BUFF, buff));
return true;
}
return false;
}
public synchronized void onTick() {
// Skip if no buffs
if (this.buffs.isEmpty()) return;
long currentTime = System.currentTimeMillis();
// Add to pending buffs to remove if buff has expired
this.buffs
.values()
.removeIf(
buff -> {
if (currentTime <= buff.getEndTime()) return false;
this.pendingBuffs.add(buff);
return true;
});
if (this.pendingBuffs.size() > 0) {
// Send packet
getPlayer()
.sendPacket(
new PacketServerBuffChangeNotify(
getPlayer(),
ServerBuffChangeType.SERVER_BUFF_CHANGE_TYPE_DEL_SERVER_BUFF,
this.pendingBuffs));
this.pendingBuffs.clear();
}
}
@Getter
public static class PlayerBuff {
private final int uid;
private final BuffData buffData;
private final long endTime;
public PlayerBuff(int uid, BuffData buffData, float duration) {
this.uid = uid;
this.buffData = buffData;
this.endTime = System.currentTimeMillis() + ((long) duration * 1000);
}
public int getGroupId() {
return getBuffData().getGroupId();
}
public ServerBuff toProto() {
return ServerBuff.newBuilder()
.setServerBuffUid(this.getUid())
.setServerBuffId(this.getBuffData().getId())
.setServerBuffType(this.getBuffData().getServerBuffType().getValue())
.setInstancedModifierId(1)
.build();
}
}
}
package emu.grasscutter.game.player;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.AbilityModifier.AbilityModifierAction;
import emu.grasscutter.data.excels.BuffData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.net.proto.ServerBuffChangeNotifyOuterClass.ServerBuffChangeNotify.ServerBuffChangeType;
import emu.grasscutter.net.proto.ServerBuffOuterClass.ServerBuff;
import emu.grasscutter.server.packet.send.PacketServerBuffChangeNotify;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import lombok.Getter;
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<>();
}
/**
* Gets a new uid for a server buff
*
* @return New integer buff uid
*/
private int getNextBuffUid() {
return ++nextBuffUid;
}
/**
* Returns true if the player has a buff with this group id
*
* @param groupId Buff group id
* @return True if a buff with this group id exists
*/
public synchronized boolean hasBuff(int groupId) {
return this.buffs.containsKey(groupId);
}
/** Clears all player buffs */
public synchronized void clearBuffs() {
// Remove from player
getPlayer()
.sendPacket(
new PacketServerBuffChangeNotify(
getPlayer(),
ServerBuffChangeType.SERVER_BUFF_CHANGE_TYPE_DEL_SERVER_BUFF,
this.buffs.values()));
// Clear
this.buffs.clear();
}
/**
* Adds a server buff to the player.
*
* @param buffId Server buff id
* @return True if a buff was added
*/
public boolean addBuff(int buffId) {
return addBuff(buffId, -1f);
}
/**
* Adds a server buff to the player.
*
* @param buffId Server buff id
* @param duration Duration of the buff in seconds. Set to 0 for an infinite buff.
* @return True if a buff was added
*/
public synchronized boolean addBuff(int buffId, float duration) {
return addBuff(buffId, duration, null);
}
/**
* Adds a server buff to the player.
*
* @param buffId Server buff id
* @param duration Duration of the buff in seconds. Set to 0 for an infinite buff.
* @param target Target avatar
* @return True if a buff was added
*/
public synchronized boolean addBuff(int buffId, float duration, Avatar target) {
// Get buff excel data
var buffData = GameData.getBuffDataMap().get(buffId);
if (buffData == null) return false;
// Perform onAdded actions
var success =
Optional.ofNullable(GameData.getAbilityData(buffData.getAbilityName()))
.map(data -> data.modifiers.get(buffData.getModifierName()))
.map(modifier -> modifier.onAdded)
.map(
onAdded -> {
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 =
ability.amount.get() + ability.amountByTargetMaxHPRatio.get() * maxHp;
target.getAsEntity().heal(amount);
shouldHeal = true;
}
}
return shouldHeal;
})
.orElse(false);
// Set duration
if (duration < 0f) {
duration = buffData.getTime();
}
// Don't add buff if duration is equal or less than 0
if (duration <= 0) {
return success;
}
// Clear previous buff if it exists
this.removeBuff(buffData.getGroupId());
// Create and store buff
PlayerBuff buff = new PlayerBuff(getNextBuffUid(), buffData, duration);
this.buffs.put(buff.getGroupId(), buff);
// Packet
getPlayer()
.sendPacket(
new PacketServerBuffChangeNotify(
getPlayer(), ServerBuffChangeType.SERVER_BUFF_CHANGE_TYPE_ADD_SERVER_BUFF, buff));
return true;
}
/**
* Removes a buff by its group id
*
* @param buffGroupId Server buff group id
* @return True if a buff was remove
*/
public synchronized boolean removeBuff(int buffGroupId) {
PlayerBuff buff = this.buffs.remove(buffGroupId);
if (buff != null) {
getPlayer()
.sendPacket(
new PacketServerBuffChangeNotify(
getPlayer(), ServerBuffChangeType.SERVER_BUFF_CHANGE_TYPE_DEL_SERVER_BUFF, buff));
return true;
}
return false;
}
public synchronized void onTick() {
// Skip if no buffs
if (this.buffs.isEmpty()) return;
long currentTime = System.currentTimeMillis();
// Add to pending buffs to remove if buff has expired
this.buffs
.values()
.removeIf(
buff -> {
if (currentTime <= buff.getEndTime()) return false;
this.pendingBuffs.add(buff);
return true;
});
if (this.pendingBuffs.size() > 0) {
// Send packet
getPlayer()
.sendPacket(
new PacketServerBuffChangeNotify(
getPlayer(),
ServerBuffChangeType.SERVER_BUFF_CHANGE_TYPE_DEL_SERVER_BUFF,
this.pendingBuffs));
this.pendingBuffs.clear();
}
}
@Getter
public static class PlayerBuff {
private final int uid;
private final BuffData buffData;
private final long endTime;
public PlayerBuff(int uid, BuffData buffData, float duration) {
this.uid = uid;
this.buffData = buffData;
this.endTime = System.currentTimeMillis() + ((long) duration * 1000);
}
public int getGroupId() {
return getBuffData().getGroupId();
}
public ServerBuff toProto() {
return ServerBuff.newBuilder()
.setServerBuffUid(this.getUid())
.setServerBuffId(this.getBuffData().getId())
.setServerBuffType(this.getBuffData().getServerBuffType().getValue())
.setInstancedModifierId(1)
.build();
}
}
}

View File

@@ -1,136 +1,136 @@
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.codex.CodexAnimalData;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.server.packet.send.PacketCodexDataUpdateNotify;
import java.util.*;
import lombok.Getter;
import lombok.val;
@Entity
public class PlayerCodex {
@Transient private Player player;
// itemId is not codexId!
@Getter private final Set<Integer> unlockedWeapon;
@Getter private final Map<Integer, Integer> unlockedAnimal;
@Getter private final Set<Integer> unlockedMaterial;
@Getter private final Set<Integer> unlockedBook;
@Getter private final Set<Integer> unlockedTip;
@Getter private final Set<Integer> unlockedView;
@Getter private Set<Integer> unlockedReliquary;
@Getter private final Set<Integer> unlockedReliquarySuitCodex;
public PlayerCodex() {
this.unlockedWeapon = new HashSet<>();
this.unlockedAnimal = new HashMap<>();
this.unlockedMaterial = new HashSet<>();
this.unlockedBook = new HashSet<>();
this.unlockedTip = new HashSet<>();
this.unlockedView = new HashSet<>();
this.unlockedReliquary = new HashSet<>();
this.unlockedReliquarySuitCodex = new HashSet<>();
}
public PlayerCodex(Player player) {
this();
this.player = player;
}
public void setPlayer(Player player) {
this.player = player;
this.fixReliquaries();
}
public void checkAddedItem(GameItem item) {
val itemData = item.getItemData();
val itemId = item.getItemId();
switch (itemData.getItemType()) {
case ITEM_WEAPON -> {
Optional.ofNullable(GameData.getCodexWeaponDataIdMap().get(itemId))
.ifPresent(
codexData -> {
if (this.getUnlockedWeapon().add(itemId)) {
this.player.save();
this.player.sendPacket(new PacketCodexDataUpdateNotify(2, codexData.getId()));
}
});
}
case ITEM_MATERIAL -> {
switch (itemData.getMaterialType()) {
// Is this check even needed?
case MATERIAL_FOOD,
MATERIAL_WIDGET,
MATERIAL_EXCHANGE,
MATERIAL_AVATAR_MATERIAL,
MATERIAL_NOTICE_ADD_HP -> {
Optional.ofNullable(GameData.getCodexMaterialDataIdMap().get(itemId))
.ifPresent(
codexData -> {
if (this.getUnlockedMaterial().add(itemId)) {
this.player.save();
this.player.sendPacket(
new PacketCodexDataUpdateNotify(4, codexData.getId()));
}
});
}
default -> {}
}
}
case ITEM_RELIQUARY -> {
val reliquaryId = (itemId / 10) * 10; // Normalize to 0-substat form
if (this.getUnlockedReliquary().add(reliquaryId)) checkUnlockedSuits(reliquaryId);
}
default -> {}
}
}
public void checkAnimal(GameEntity target, CodexAnimalData.CountType countType) {
if (target instanceof EntityMonster) {
val monsterId = ((EntityMonster) target).getMonsterData().getId();
val codexAnimal = GameData.getCodexAnimalDataMap().get(monsterId);
if (codexAnimal == null) return;
val animalCountType = codexAnimal.getCountType();
if (animalCountType != countType && animalCountType != null) return;
this.getUnlockedAnimal().merge(monsterId, 1, (i, j) -> i + 1);
player.save();
this.player.sendPacket(new PacketCodexDataUpdateNotify(3, monsterId));
}
}
public void checkUnlockedSuits(int reliquaryId) {
GameData.getCodexReliquaryArrayList().stream()
.filter(x -> !this.getUnlockedReliquarySuitCodex().contains(x.getId()))
.filter(x -> x.containsId(reliquaryId))
.filter(x -> this.getUnlockedReliquary().containsAll(x.getIds()))
.forEach(
x -> {
int id = x.getId();
this.getUnlockedReliquarySuitCodex().add(id);
this.player.save();
this.player.sendPacket(new PacketCodexDataUpdateNotify(8, id));
});
}
@Deprecated // Maybe remove this if we ever stop caring about older dbs
private void fixReliquaries() {
// Migrate older database entries which were using non-canonical forms of itemIds
val newReliquaries = new HashSet<Integer>();
this.unlockedReliquary.forEach(i -> newReliquaries.add((i / 10) * 10));
this.unlockedReliquary = newReliquaries;
GameData.getCodexReliquaryArrayList().stream()
.filter(x -> !this.getUnlockedReliquarySuitCodex().contains(x.getId()))
.filter(x -> this.getUnlockedReliquary().containsAll(x.getIds()))
.forEach(x -> this.getUnlockedReliquarySuitCodex().add(x.getId()));
this.player.save();
}
}
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.codex.CodexAnimalData;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.entity.GameEntity;
import emu.grasscutter.game.inventory.GameItem;
import emu.grasscutter.server.packet.send.PacketCodexDataUpdateNotify;
import java.util.*;
import lombok.Getter;
import lombok.val;
@Entity
public class PlayerCodex {
@Transient private Player player;
// itemId is not codexId!
@Getter private final Set<Integer> unlockedWeapon;
@Getter private final Map<Integer, Integer> unlockedAnimal;
@Getter private final Set<Integer> unlockedMaterial;
@Getter private final Set<Integer> unlockedBook;
@Getter private final Set<Integer> unlockedTip;
@Getter private final Set<Integer> unlockedView;
@Getter private Set<Integer> unlockedReliquary;
@Getter private final Set<Integer> unlockedReliquarySuitCodex;
public PlayerCodex() {
this.unlockedWeapon = new HashSet<>();
this.unlockedAnimal = new HashMap<>();
this.unlockedMaterial = new HashSet<>();
this.unlockedBook = new HashSet<>();
this.unlockedTip = new HashSet<>();
this.unlockedView = new HashSet<>();
this.unlockedReliquary = new HashSet<>();
this.unlockedReliquarySuitCodex = new HashSet<>();
}
public PlayerCodex(Player player) {
this();
this.player = player;
}
public void setPlayer(Player player) {
this.player = player;
this.fixReliquaries();
}
public void checkAddedItem(GameItem item) {
val itemData = item.getItemData();
val itemId = item.getItemId();
switch (itemData.getItemType()) {
case ITEM_WEAPON -> {
Optional.ofNullable(GameData.getCodexWeaponDataIdMap().get(itemId))
.ifPresent(
codexData -> {
if (this.getUnlockedWeapon().add(itemId)) {
this.player.save();
this.player.sendPacket(new PacketCodexDataUpdateNotify(2, codexData.getId()));
}
});
}
case ITEM_MATERIAL -> {
switch (itemData.getMaterialType()) {
// Is this check even needed?
case MATERIAL_FOOD,
MATERIAL_WIDGET,
MATERIAL_EXCHANGE,
MATERIAL_AVATAR_MATERIAL,
MATERIAL_NOTICE_ADD_HP -> {
Optional.ofNullable(GameData.getCodexMaterialDataIdMap().get(itemId))
.ifPresent(
codexData -> {
if (this.getUnlockedMaterial().add(itemId)) {
this.player.save();
this.player.sendPacket(
new PacketCodexDataUpdateNotify(4, codexData.getId()));
}
});
}
default -> {}
}
}
case ITEM_RELIQUARY -> {
val reliquaryId = (itemId / 10) * 10; // Normalize to 0-substat form
if (this.getUnlockedReliquary().add(reliquaryId)) checkUnlockedSuits(reliquaryId);
}
default -> {}
}
}
public void checkAnimal(GameEntity target, CodexAnimalData.CountType countType) {
if (target instanceof EntityMonster) {
val monsterId = ((EntityMonster) target).getMonsterData().getId();
val codexAnimal = GameData.getCodexAnimalDataMap().get(monsterId);
if (codexAnimal == null) return;
val animalCountType = codexAnimal.getCountType();
if (animalCountType != countType && animalCountType != null) return;
this.getUnlockedAnimal().merge(monsterId, 1, (i, j) -> i + 1);
player.save();
this.player.sendPacket(new PacketCodexDataUpdateNotify(3, monsterId));
}
}
public void checkUnlockedSuits(int reliquaryId) {
GameData.getCodexReliquaryArrayList().stream()
.filter(x -> !this.getUnlockedReliquarySuitCodex().contains(x.getId()))
.filter(x -> x.containsId(reliquaryId))
.filter(x -> this.getUnlockedReliquary().containsAll(x.getIds()))
.forEach(
x -> {
int id = x.getId();
this.getUnlockedReliquarySuitCodex().add(id);
this.player.save();
this.player.sendPacket(new PacketCodexDataUpdateNotify(8, id));
});
}
@Deprecated // Maybe remove this if we ever stop caring about older dbs
private void fixReliquaries() {
// Migrate older database entries which were using non-canonical forms of itemIds
val newReliquaries = new HashSet<Integer>();
this.unlockedReliquary.forEach(i -> newReliquaries.add((i / 10) * 10));
this.unlockedReliquary = newReliquaries;
GameData.getCodexReliquaryArrayList().stream()
.filter(x -> !this.getUnlockedReliquarySuitCodex().contains(x.getId()))
.filter(x -> this.getUnlockedReliquary().containsAll(x.getIds()))
.forEach(x -> this.getUnlockedReliquarySuitCodex().add(x.getId()));
this.player.save();
}
}

View File

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

@@ -1,45 +1,45 @@
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;
import lombok.AllArgsConstructor;
import lombok.Getter;
@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<>();
static {
Stream.of(values())
.forEach(
e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
public static ActivityType getTypeByValue(int value) {
return map.getOrDefault(value, NONE);
}
public static ActivityType getTypeByName(String name) {
return stringMap.getOrDefault(name, 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;
import lombok.AllArgsConstructor;
import lombok.Getter;
@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<>();
static {
Stream.of(values())
.forEach(
e -> {
map.put(e.getValue(), e);
stringMap.put(e.name(), e);
});
}
public static ActivityType getTypeByValue(int value) {
return map.getOrDefault(value, NONE);
}
public static ActivityType getTypeByName(String name) {
return stringMap.getOrDefault(name, NONE);
}
}

View File

@@ -1,131 +1,131 @@
package emu.grasscutter.game.props;
import emu.grasscutter.scripts.constants.IntValueEnum;
import emu.grasscutter.utils.Utils;
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;
import lombok.Getter;
public enum ElementType implements IntValueEnum {
None(0, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY),
Fire(
1,
FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY,
FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY,
10101,
"TeamResonance_Fire_Lv2",
1),
Water(
2,
FightProperty.FIGHT_PROP_CUR_WATER_ENERGY,
FightProperty.FIGHT_PROP_MAX_WATER_ENERGY,
10201,
"TeamResonance_Water_Lv2",
2),
Grass(
3,
FightProperty.FIGHT_PROP_CUR_GRASS_ENERGY,
FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY,
10501,
"TeamResonance_Grass_Lv2",
7),
Electric(
4,
FightProperty.FIGHT_PROP_CUR_ELEC_ENERGY,
FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY,
10401,
"TeamResonance_Electric_Lv2",
6),
Ice(
5,
FightProperty.FIGHT_PROP_CUR_ICE_ENERGY,
FightProperty.FIGHT_PROP_MAX_ICE_ENERGY,
10601,
"TeamResonance_Ice_Lv2",
4),
Frozen(6, FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY),
Wind(
7,
FightProperty.FIGHT_PROP_CUR_WIND_ENERGY,
FightProperty.FIGHT_PROP_MAX_WIND_ENERGY,
10301,
"TeamResonance_Wind_Lv2",
3),
Rock(
8,
FightProperty.FIGHT_PROP_CUR_ROCK_ENERGY,
FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY,
10701,
"TeamResonance_Rock_Lv2",
5),
AntiFire(9, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY),
Default(
255,
FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY,
FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY,
10801,
"TeamResonance_AllDifferent");
private static final Int2ObjectMap<ElementType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, ElementType> stringMap = new HashMap<>();
static {
// Create bindings for each value.
Stream.of(ElementType.values())
.forEach(
entry -> {
map.put(entry.getValue(), entry);
stringMap.put(entry.name(), entry);
});
}
@Getter private final int value;
@Getter private final int teamResonanceId;
@Getter private final FightProperty curEnergyProp;
@Getter private final FightProperty maxEnergyProp;
@Getter private final int depotIndex;
@Getter private final int configHash;
ElementType(int value, FightProperty curEnergyProp, FightProperty maxEnergyProp) {
this(value, curEnergyProp, maxEnergyProp, 0, null, 1);
}
ElementType(
int value,
FightProperty curEnergyProp,
FightProperty maxEnergyProp,
int teamResonanceId,
String configName) {
this(value, curEnergyProp, maxEnergyProp, teamResonanceId, configName, 1);
}
ElementType(
int value,
FightProperty curEnergyProp,
FightProperty maxEnergyProp,
int teamResonanceId,
String configName,
int depotIndex) {
this.value = value;
this.curEnergyProp = curEnergyProp;
this.maxEnergyProp = maxEnergyProp;
this.teamResonanceId = teamResonanceId;
this.depotIndex = depotIndex;
if (configName != null) {
this.configHash = Utils.abilityHash(configName);
} else {
this.configHash = 0;
}
}
public static ElementType getTypeByValue(int value) {
return map.getOrDefault(value, None);
}
public static ElementType getTypeByName(String name) {
return stringMap.getOrDefault(name, None);
}
}
package emu.grasscutter.game.props;
import emu.grasscutter.scripts.constants.IntValueEnum;
import emu.grasscutter.utils.Utils;
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;
import lombok.Getter;
public enum ElementType implements IntValueEnum {
None(0, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY),
Fire(
1,
FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY,
FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY,
10101,
"TeamResonance_Fire_Lv2",
1),
Water(
2,
FightProperty.FIGHT_PROP_CUR_WATER_ENERGY,
FightProperty.FIGHT_PROP_MAX_WATER_ENERGY,
10201,
"TeamResonance_Water_Lv2",
2),
Grass(
3,
FightProperty.FIGHT_PROP_CUR_GRASS_ENERGY,
FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY,
10501,
"TeamResonance_Grass_Lv2",
7),
Electric(
4,
FightProperty.FIGHT_PROP_CUR_ELEC_ENERGY,
FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY,
10401,
"TeamResonance_Electric_Lv2",
6),
Ice(
5,
FightProperty.FIGHT_PROP_CUR_ICE_ENERGY,
FightProperty.FIGHT_PROP_MAX_ICE_ENERGY,
10601,
"TeamResonance_Ice_Lv2",
4),
Frozen(6, FightProperty.FIGHT_PROP_CUR_ICE_ENERGY, FightProperty.FIGHT_PROP_MAX_ICE_ENERGY),
Wind(
7,
FightProperty.FIGHT_PROP_CUR_WIND_ENERGY,
FightProperty.FIGHT_PROP_MAX_WIND_ENERGY,
10301,
"TeamResonance_Wind_Lv2",
3),
Rock(
8,
FightProperty.FIGHT_PROP_CUR_ROCK_ENERGY,
FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY,
10701,
"TeamResonance_Rock_Lv2",
5),
AntiFire(9, FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY, FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY),
Default(
255,
FightProperty.FIGHT_PROP_CUR_FIRE_ENERGY,
FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY,
10801,
"TeamResonance_AllDifferent");
private static final Int2ObjectMap<ElementType> map = new Int2ObjectOpenHashMap<>();
private static final Map<String, ElementType> stringMap = new HashMap<>();
static {
// Create bindings for each value.
Stream.of(ElementType.values())
.forEach(
entry -> {
map.put(entry.getValue(), entry);
stringMap.put(entry.name(), entry);
});
}
@Getter private final int value;
@Getter private final int teamResonanceId;
@Getter private final FightProperty curEnergyProp;
@Getter private final FightProperty maxEnergyProp;
@Getter private final int depotIndex;
@Getter private final int configHash;
ElementType(int value, FightProperty curEnergyProp, FightProperty maxEnergyProp) {
this(value, curEnergyProp, maxEnergyProp, 0, null, 1);
}
ElementType(
int value,
FightProperty curEnergyProp,
FightProperty maxEnergyProp,
int teamResonanceId,
String configName) {
this(value, curEnergyProp, maxEnergyProp, teamResonanceId, configName, 1);
}
ElementType(
int value,
FightProperty curEnergyProp,
FightProperty maxEnergyProp,
int teamResonanceId,
String configName,
int depotIndex) {
this.value = value;
this.curEnergyProp = curEnergyProp;
this.maxEnergyProp = maxEnergyProp;
this.teamResonanceId = teamResonanceId;
this.depotIndex = depotIndex;
if (configName != null) {
this.configHash = Utils.abilityHash(configName);
} else {
this.configHash = 0;
}
}
public static ElementType getTypeByValue(int value) {
return map.getOrDefault(value, None);
}
public static ElementType getTypeByName(String name) {
return stringMap.getOrDefault(name, None);
}
}

View File

@@ -1,62 +1,62 @@
package emu.grasscutter.game.props.ItemUseAction;
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;
public abstract class ItemUseAddEnergy extends ItemUseAction {
public abstract float getAddEnergy(ElementType avatarElement);
public float getAddEnergy(AvatarSkillDepotData depot) {
if (depot == null) return 0f;
var element = depot.getElementType();
if (element == null) return 0f;
return this.getAddEnergy(element);
}
@Override
public boolean useItem(UseItemParams params) {
var teamManager = params.player.getTeamManager();
return switch (params.itemUseTarget) {
case ITEM_USE_TARGET_CUR_AVATAR -> {
this.addEnergy(teamManager.getCurrentAvatarEntity().getAvatar(), params.count);
yield true; // Always consume elem balls
}
case ITEM_USE_TARGET_CUR_TEAM -> {
var activeTeam = teamManager.getActiveTeam();
// On-field vs off-field multiplier.
// The on-field character gets full amount, off-field characters get less depending on the
// team size.
final float offFieldRatio =
switch (activeTeam.size()) {
case 2 -> 0.8f;
case 3 -> 0.7f;
default -> 0.6f;
};
final int currentCharacterIndex = teamManager.getCurrentCharacterIndex();
// Add energy to every team member.
for (int i = 0; i < activeTeam.size(); i++) {
var avatar = activeTeam.get(i).getAvatar();
if (i == currentCharacterIndex) this.addEnergy(avatar, params.count);
else this.addEnergy(avatar, params.count * offFieldRatio);
}
yield true; // Always consume elem balls
}
case ITEM_USE_TARGET_SPECIFY_AVATAR,
ITEM_USE_TARGET_SPECIFY_ALIVE_AVATAR,
ITEM_USE_TARGET_SPECIFY_DEAD_AVATAR -> this.addEnergy(
params.targetAvatar, params.count); // Targeted items might care about this
case ITEM_USE_TARGET_NONE -> false;
};
}
private boolean addEnergy(Avatar avatar, float multiplier) {
float energy = this.getAddEnergy(avatar.getSkillDepot()) * multiplier;
if (energy < 0.01f) return false;
avatar.getAsEntity().addEnergy(energy, PropChangeReason.PROP_CHANGE_REASON_ENERGY_BALL);
return true;
}
}
package emu.grasscutter.game.props.ItemUseAction;
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;
public abstract class ItemUseAddEnergy extends ItemUseAction {
public abstract float getAddEnergy(ElementType avatarElement);
public float getAddEnergy(AvatarSkillDepotData depot) {
if (depot == null) return 0f;
var element = depot.getElementType();
if (element == null) return 0f;
return this.getAddEnergy(element);
}
@Override
public boolean useItem(UseItemParams params) {
var teamManager = params.player.getTeamManager();
return switch (params.itemUseTarget) {
case ITEM_USE_TARGET_CUR_AVATAR -> {
this.addEnergy(teamManager.getCurrentAvatarEntity().getAvatar(), params.count);
yield true; // Always consume elem balls
}
case ITEM_USE_TARGET_CUR_TEAM -> {
var activeTeam = teamManager.getActiveTeam();
// On-field vs off-field multiplier.
// The on-field character gets full amount, off-field characters get less depending on the
// team size.
final float offFieldRatio =
switch (activeTeam.size()) {
case 2 -> 0.8f;
case 3 -> 0.7f;
default -> 0.6f;
};
final int currentCharacterIndex = teamManager.getCurrentCharacterIndex();
// Add energy to every team member.
for (int i = 0; i < activeTeam.size(); i++) {
var avatar = activeTeam.get(i).getAvatar();
if (i == currentCharacterIndex) this.addEnergy(avatar, params.count);
else this.addEnergy(avatar, params.count * offFieldRatio);
}
yield true; // Always consume elem balls
}
case ITEM_USE_TARGET_SPECIFY_AVATAR,
ITEM_USE_TARGET_SPECIFY_ALIVE_AVATAR,
ITEM_USE_TARGET_SPECIFY_DEAD_AVATAR -> this.addEnergy(
params.targetAvatar, params.count); // Targeted items might care about this
case ITEM_USE_TARGET_NONE -> false;
};
}
private boolean addEnergy(Avatar avatar, float multiplier) {
float energy = this.getAddEnergy(avatar.getSkillDepot()) * multiplier;
if (energy < 0.01f) return false;
avatar.getAsEntity().addEnergy(energy, PropChangeReason.PROP_CHANGE_REASON_ENERGY_BALL);
return true;
}
}

View File

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

@@ -120,7 +120,8 @@ public class QuestManager extends BasePlayerManager {
quest.checkProgress();
}
player.getActivityManager().triggerActivityConditions();
if (this.player.getActivityManager() != null)
this.player.getActivityManager().triggerActivityConditions();
}
public void onTick(){
@@ -132,7 +133,7 @@ public class QuestManager extends BasePlayerManager {
0);
}
private void checkTimeVars(){
private void checkTimeVars() {
val currentDays = player.getWorld().getTotalGameTimeDays();
val currentHours = player.getWorld().getTotalGameTimeHours();
boolean checkDays = currentDays != lastDayCheck;
@@ -144,6 +145,7 @@ public class QuestManager extends BasePlayerManager {
this.lastDayCheck = currentDays;
this.lastHourCheck = currentHours;
player.getActiveQuestTimers().forEach(mainQuestId -> {
if(checkHours) {
queueEvent(QuestCond.QUEST_COND_TIME_VAR_GT_EQ, mainQuestId);

View File

@@ -1,136 +1,136 @@
package emu.grasscutter.game.quest;
import emu.grasscutter.Grasscutter;
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.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;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.reflections.Reflections;
@SuppressWarnings("unchecked")
public class QuestSystem extends BaseGameSystem {
private final Int2ObjectMap<BaseCondition> condHandlers;
private final Int2ObjectMap<BaseContent> contHandlers;
private final Int2ObjectMap<QuestExecHandler> execHandlers;
public QuestSystem(GameServer server) {
super(server);
this.condHandlers = new Int2ObjectOpenHashMap<>();
this.contHandlers = new Int2ObjectOpenHashMap<>();
this.execHandlers = new Int2ObjectOpenHashMap<>();
this.registerHandlers();
}
public void registerHandlers() {
this.registerHandlers(
this.condHandlers, "emu.grasscutter.game.quest.conditions", BaseCondition.class);
this.registerHandlers(
this.contHandlers, "emu.grasscutter.game.quest.content", BaseContent.class);
this.registerHandlers(
this.execHandlers, "emu.grasscutter.game.quest.exec", QuestExecHandler.class);
}
public <T> void registerHandlers(Int2ObjectMap<T> map, String packageName, Class<T> clazz) {
Reflections reflections = new Reflections(packageName);
var handlerClasses = reflections.getSubTypesOf(clazz);
for (var obj : handlerClasses) {
this.registerPacketHandler(map, obj);
}
}
public <T> void registerPacketHandler(Int2ObjectMap<T> map, Class<? extends T> handlerClass) {
try {
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;
}
if (value <= 0) {
return;
}
map.put(value, handlerClass.getDeclaredConstructor().newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
// TODO make cleaner
public boolean triggerCondition(
Player owner,
QuestData questData,
QuestAcceptCondition condition,
String paramStr,
int... params) {
BaseCondition handler = condHandlers.get(condition.getType().getValue());
if (handler == null || questData == null) {
Grasscutter.getLogger()
.debug("Could not trigger condition {} at {}", condition.getType().getValue(), questData);
return false;
}
return handler.execute(owner, questData, condition, paramStr, params);
}
public boolean triggerContent(
GameQuest quest, QuestContentCondition condition, String paramStr, int... params) {
BaseContent handler = contHandlers.get(condition.getType().getValue());
if (handler == null || quest.getQuestData() == null) {
Grasscutter.getLogger()
.debug(
"Could not trigger content {} at {}",
condition.getType().getValue(),
quest.getQuestData());
return false;
}
return handler.execute(quest, condition, paramStr, params);
}
public void triggerExec(GameQuest quest, QuestExecParam execParam, String... params) {
QuestExecHandler handler = execHandlers.get(execParam.getType().getValue());
if (handler == null || quest.getQuestData() == null) {
Grasscutter.getLogger()
.debug(
"Could not trigger exec {} at {}",
execParam.getType().getValue(),
quest.getQuestData());
return;
}
QuestManager.eventExecutor.submit(
() -> {
if (!handler.execute(quest, execParam, params)) {
Grasscutter.getLogger()
.debug(
"exec trigger failed {} at {}",
execParam.getType().getValue(),
quest.getQuestData());
}
});
}
}
package emu.grasscutter.game.quest;
import emu.grasscutter.Grasscutter;
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.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;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.reflections.Reflections;
@SuppressWarnings("unchecked")
public class QuestSystem extends BaseGameSystem {
private final Int2ObjectMap<BaseCondition> condHandlers;
private final Int2ObjectMap<BaseContent> contHandlers;
private final Int2ObjectMap<QuestExecHandler> execHandlers;
public QuestSystem(GameServer server) {
super(server);
this.condHandlers = new Int2ObjectOpenHashMap<>();
this.contHandlers = new Int2ObjectOpenHashMap<>();
this.execHandlers = new Int2ObjectOpenHashMap<>();
this.registerHandlers();
}
public void registerHandlers() {
this.registerHandlers(
this.condHandlers, "emu.grasscutter.game.quest.conditions", BaseCondition.class);
this.registerHandlers(
this.contHandlers, "emu.grasscutter.game.quest.content", BaseContent.class);
this.registerHandlers(
this.execHandlers, "emu.grasscutter.game.quest.exec", QuestExecHandler.class);
}
public <T> void registerHandlers(Int2ObjectMap<T> map, String packageName, Class<T> clazz) {
Reflections reflections = new Reflections(packageName);
var handlerClasses = reflections.getSubTypesOf(clazz);
for (var obj : handlerClasses) {
this.registerPacketHandler(map, obj);
}
}
public <T> void registerPacketHandler(Int2ObjectMap<T> map, Class<? extends T> handlerClass) {
try {
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;
}
if (value <= 0) {
return;
}
map.put(value, handlerClass.getDeclaredConstructor().newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
// TODO make cleaner
public boolean triggerCondition(
Player owner,
QuestData questData,
QuestAcceptCondition condition,
String paramStr,
int... params) {
BaseCondition handler = condHandlers.get(condition.getType().getValue());
if (handler == null || questData == null) {
Grasscutter.getLogger()
.debug("Could not trigger condition {} at {}", condition.getType().getValue(), questData);
return false;
}
return handler.execute(owner, questData, condition, paramStr, params);
}
public boolean triggerContent(
GameQuest quest, QuestContentCondition condition, String paramStr, int... params) {
BaseContent handler = contHandlers.get(condition.getType().getValue());
if (handler == null || quest.getQuestData() == null) {
Grasscutter.getLogger()
.debug(
"Could not trigger content {} at {}",
condition.getType().getValue(),
quest.getQuestData());
return false;
}
return handler.execute(quest, condition, paramStr, params);
}
public void triggerExec(GameQuest quest, QuestExecParam execParam, String... params) {
QuestExecHandler handler = execHandlers.get(execParam.getType().getValue());
if (handler == null || quest.getQuestData() == null) {
Grasscutter.getLogger()
.debug(
"Could not trigger exec {} at {}",
execParam.getType().getValue(),
quest.getQuestData());
return;
}
QuestManager.eventExecutor.submit(
() -> {
if (!handler.execute(quest, execParam, params)) {
Grasscutter.getLogger()
.debug(
"exec trigger failed {} at {}",
execParam.getType().getValue(),
quest.getQuestData());
}
});
}
}

View File

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

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