Format code

This commit is contained in:
KingRainbow44
2023-04-02 21:34:07 -04:00
parent b03870ab48
commit a3970f8e43
104 changed files with 15623 additions and 15004 deletions

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();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +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,314 +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 lombok.Getter;
import lombok.NonNull;
import lombok.val;
import javax.annotation.Nullable;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* TODO handle time limits
* TODO handle respawn points
* TODO handle team wipes and respawns
* TODO check monster level and levelConfigMap
*/
public 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,157 +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 lombok.val;
import org.reflections.Reflections;
import java.util.List;
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,38 +1,40 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult.DungeonEndReason;
import emu.grasscutter.game.world.SceneGroupInstance;
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,167 +1,178 @@
package emu.grasscutter.game.dungeons.challenge;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.SceneTrigger;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketDungeonChallengeBeginNotify;
import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
@Getter
@Setter
public class WorldChallenge {
private final Scene scene;
private final SceneGroup group;
private final int challengeId;
private final int challengeIndex;
private final List<Integer> paramList;
private final int timeLimit;
private final List<ChallengeTrigger> challengeTriggers;
private final int goal;
private final AtomicInteger score;
private boolean progress;
private boolean success;
private long startedAt;
private int finishedTime;
public WorldChallenge(
Scene scene,
SceneGroup group,
int challengeId,
int challengeIndex,
List<Integer> paramList,
int timeLimit,
int goal,
List<ChallengeTrigger> challengeTriggers) {
this.scene = scene;
this.group = group;
this.challengeId = challengeId;
this.challengeIndex = challengeIndex;
this.paramList = paramList;
this.timeLimit = timeLimit;
this.challengeTriggers = challengeTriggers;
this.goal = goal;
this.score = new AtomicInteger(0);
}
public boolean inProgress() {
return this.progress;
}
public void onCheckTimeOut() {
if (!inProgress()) {
return;
}
if (timeLimit <= 0) {
return;
}
challengeTriggers.forEach(t -> t.onCheckTimeout(this));
}
public void start() {
if (inProgress()) {
Grasscutter.getLogger().info("Could not start a in progress challenge.");
return;
}
this.progress = true;
this.startedAt = System.currentTimeMillis();
getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this));
challengeTriggers.forEach(t -> t.onBegin(this));
}
public void done() {
if (!this.inProgress()) return;
this.finish(true);
var scene = this.getScene();
var dungeonManager = scene.getDungeonManager();
if (dungeonManager != null && dungeonManager.getDungeonData() != null) {
scene.getPlayers().forEach(p -> p.getActivityManager().triggerWatcher(
WatcherTriggerType.TRIGGER_FINISH_CHALLENGE,
String.valueOf(dungeonManager.getDungeonData().getId()),
String.valueOf(this.getGroup().id),
String.valueOf(this.getChallengeId())
));
}
scene.getScriptManager().callEvent(
// TODO record the time in PARAM2 and used in action
new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_SUCCESS).setParam2(finishedTime));
this.getScene().triggerDungeonEvent(DungeonPassConditionType.DUNGEON_COND_FINISH_CHALLENGE, getChallengeId(), getChallengeIndex());
this.challengeTriggers.forEach(t -> t.onFinish(this));
}
public void fail(){
if (!this.inProgress()) return;
this.finish(true);
this.getScene().getScriptManager().callEvent(new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_FAIL));
challengeTriggers.forEach(t -> t.onFinish(this));
}
private void finish(boolean success) {
this.progress = false;
this.success = success;
this.finishedTime = (int) ((System.currentTimeMillis() - this.startedAt) / 1000L);
getScene().broadcastPacket(new PacketDungeonChallengeFinishNotify(this));
}
public int increaseScore() {
return score.incrementAndGet();
}
public void onMonsterDeath(EntityMonster monster) {
if (!inProgress()) {
return;
}
if (monster.getGroupId() != getGroup().id) {
return;
}
this.challengeTriggers.forEach(t -> t.onMonsterDeath(this, monster));
}
public void onGadgetDeath(EntityGadget gadget) {
if (!inProgress()) {
return;
}
if (gadget.getGroupId() != getGroup().id) {
return;
}
this.challengeTriggers.forEach(t -> t.onGadgetDeath(this, gadget));
}
public void onGroupTriggerDeath(SceneTrigger trigger) {
if(!this.inProgress()) return;
var triggerGroup = trigger.getCurrentGroup();
if (triggerGroup == null ||
triggerGroup.id != getGroup().id) {
return;
}
this.challengeTriggers.forEach(t -> t.onGroupTrigger(this, trigger));
}
public void onGadgetDamage(EntityGadget gadget) {
if (!inProgress()) {
return;
}
if (gadget.getGroupId() != getGroup().id) {
return;
}
this.challengeTriggers.forEach(t -> t.onGadgetDamage(this, gadget));
}
}
package emu.grasscutter.game.dungeons.challenge;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.game.props.WatcherTriggerType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.scripts.constants.EventType;
import emu.grasscutter.scripts.data.SceneGroup;
import emu.grasscutter.scripts.data.SceneTrigger;
import emu.grasscutter.scripts.data.ScriptArgs;
import emu.grasscutter.server.packet.send.PacketDungeonChallengeBeginNotify;
import emu.grasscutter.server.packet.send.PacketDungeonChallengeFinishNotify;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class WorldChallenge {
private final Scene scene;
private final SceneGroup group;
private final int challengeId;
private final int challengeIndex;
private final List<Integer> paramList;
private final int timeLimit;
private final List<ChallengeTrigger> challengeTriggers;
private final int goal;
private final AtomicInteger score;
private boolean progress;
private boolean success;
private long startedAt;
private int finishedTime;
public WorldChallenge(
Scene scene,
SceneGroup group,
int challengeId,
int challengeIndex,
List<Integer> paramList,
int timeLimit,
int goal,
List<ChallengeTrigger> challengeTriggers) {
this.scene = scene;
this.group = group;
this.challengeId = challengeId;
this.challengeIndex = challengeIndex;
this.paramList = paramList;
this.timeLimit = timeLimit;
this.challengeTriggers = challengeTriggers;
this.goal = goal;
this.score = new AtomicInteger(0);
}
public boolean inProgress() {
return this.progress;
}
public void onCheckTimeOut() {
if (!inProgress()) {
return;
}
if (timeLimit <= 0) {
return;
}
challengeTriggers.forEach(t -> t.onCheckTimeout(this));
}
public void start() {
if (inProgress()) {
Grasscutter.getLogger().info("Could not start a in progress challenge.");
return;
}
this.progress = true;
this.startedAt = System.currentTimeMillis();
getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this));
challengeTriggers.forEach(t -> t.onBegin(this));
}
public void done() {
if (!this.inProgress()) return;
this.finish(true);
var scene = this.getScene();
var dungeonManager = scene.getDungeonManager();
if (dungeonManager != null && dungeonManager.getDungeonData() != null) {
scene
.getPlayers()
.forEach(
p ->
p.getActivityManager()
.triggerWatcher(
WatcherTriggerType.TRIGGER_FINISH_CHALLENGE,
String.valueOf(dungeonManager.getDungeonData().getId()),
String.valueOf(this.getGroup().id),
String.valueOf(this.getChallengeId())));
}
scene
.getScriptManager()
.callEvent(
// TODO record the time in PARAM2 and used in action
new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_SUCCESS)
.setParam2(finishedTime));
this.getScene()
.triggerDungeonEvent(
DungeonPassConditionType.DUNGEON_COND_FINISH_CHALLENGE,
getChallengeId(),
getChallengeIndex());
this.challengeTriggers.forEach(t -> t.onFinish(this));
}
public void fail() {
if (!this.inProgress()) return;
this.finish(true);
this.getScene()
.getScriptManager()
.callEvent(new ScriptArgs(this.getGroup().id, EventType.EVENT_CHALLENGE_FAIL));
challengeTriggers.forEach(t -> t.onFinish(this));
}
private void finish(boolean success) {
this.progress = false;
this.success = success;
this.finishedTime = (int) ((System.currentTimeMillis() - this.startedAt) / 1000L);
getScene().broadcastPacket(new PacketDungeonChallengeFinishNotify(this));
}
public int increaseScore() {
return score.incrementAndGet();
}
public void onMonsterDeath(EntityMonster monster) {
if (!inProgress()) {
return;
}
if (monster.getGroupId() != getGroup().id) {
return;
}
this.challengeTriggers.forEach(t -> t.onMonsterDeath(this, monster));
}
public void onGadgetDeath(EntityGadget gadget) {
if (!inProgress()) {
return;
}
if (gadget.getGroupId() != getGroup().id) {
return;
}
this.challengeTriggers.forEach(t -> t.onGadgetDeath(this, gadget));
}
public void onGroupTriggerDeath(SceneTrigger trigger) {
if (!this.inProgress()) return;
var triggerGroup = trigger.getCurrentGroup();
if (triggerGroup == null || triggerGroup.id != getGroup().id) {
return;
}
this.challengeTriggers.forEach(t -> t.onGroupTrigger(this, trigger));
}
public void onGadgetDamage(EntityGadget gadget) {
if (!inProgress()) {
return;
}
if (gadget.getGroupId() != getGroup().id) {
return;
}
this.challengeTriggers.forEach(t -> t.onGadgetDamage(this, gadget));
}
}

View File

@@ -1,36 +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 lombok.val;
import java.util.ArrayList;
import java.util.List;
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

@@ -7,5 +7,14 @@ 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);
WorldChallenge build(
int challengeIndex,
int challengeId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group);
}

View File

@@ -1,18 +1,17 @@
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;
import java.util.List;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_KILL_COUNT_GUARD_HP;
public class KillAndGuardChallengeFactoryHandler implements ChallengeFactoryHandler{
public class KillAndGuardChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override
public boolean isThisType(ChallengeType challengeType) {
// ActiveChallenge with 1,188,234101003,12,3030,0
@@ -20,15 +19,24 @@ public class KillAndGuardChallengeFactoryHandler implements ChallengeFactoryHand
}
@Override /*TODO check param4 == monstesToKill*/
public WorldChallenge build(int challengeIndex, int challengeId, int groupId, int monstersToKill, int gadgetCFGId, int unused, Scene scene, SceneGroup group) {
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,
scene,
realGroup,
challengeId, // Id
challengeIndex, // Index
List.of(monstersToKill, 0),
0, // Limit
monstersToKill, // Goal
monstersToKill, // Goal
List.of(new KillMonsterCountTrigger(), new GuardTrigger(gadgetCFGId)));
}
}

View File

@@ -5,11 +5,10 @@ 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;
import java.util.List;
public class KillMonsterCountChallengeFactoryHandler implements ChallengeFactoryHandler{
public class KillMonsterCountChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override
public boolean isThisType(ChallengeType challengeType) {
// ActiveChallenge with 1, 1, 241033003, 15, 0, 0
@@ -17,16 +16,24 @@ public class KillMonsterCountChallengeFactoryHandler implements ChallengeFactory
}
@Override
public WorldChallenge build(int challengeIndex, int challengeId, int groupId, int goal, int param5, int param6, Scene scene, SceneGroup group) {
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,
scene,
realGroup,
challengeId, // Id
challengeIndex, // Index
List.of(goal, groupId),
0, // Limit
goal, // Goal
List.of(new KillMonsterCountTrigger())
);
goal, // Goal
List.of(new KillMonsterCountTrigger()));
}
}

View File

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

View File

@@ -6,30 +6,37 @@ 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;
import java.util.List;
public class KillMonsterTimeChallengeFactoryHandler implements ChallengeFactoryHandler{
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;
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) {
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,
scene,
realGroup,
challengeId, // Id
challengeIndex, // Index
List.of(targetCount, timeLimit),
timeLimit, // Limit
targetCount, // Goal
List.of(new KillMonsterCountTrigger(), new InTimeTrigger())
);
targetCount, // Goal
List.of(new KillMonsterCountTrigger(), new InTimeTrigger()));
}
}

View File

@@ -1,15 +1,14 @@
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;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_SURVIVE;
public class SurviveChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override
public boolean isThisType(ChallengeType challengeType) {
@@ -19,15 +18,23 @@ public class SurviveChallengeFactoryHandler implements ChallengeFactoryHandler {
}
@Override
public WorldChallenge build(int challengeIndex, int challengeId, int timeToSurvive, int unused4, int unused5, int unused6, Scene scene, SceneGroup group) {
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())
);
scene,
group,
challengeId, // Id
challengeIndex, // Index
List.of(timeToSurvive),
timeToSurvive, // Limit
0, // Goal
List.of(new ForTimeTrigger()));
}
}

View File

@@ -1,16 +1,15 @@
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;
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_TRIGGER_IN_TIME;
public class TriggerInTimeChallengeFactoryHandler implements ChallengeFactoryHandler {
@Override
public boolean isThisType(ChallengeType challengeType) {
@@ -22,15 +21,23 @@ public class TriggerInTimeChallengeFactoryHandler implements ChallengeFactoryHan
}
@Override
public WorldChallenge build(int challengeIndex, int challengeId, int timeLimit, int param4, int triggerTag, int triggerCount, Scene scene, SceneGroup group) {
public WorldChallenge build(
int challengeIndex,
int challengeId,
int timeLimit,
int param4,
int triggerTag,
int triggerCount,
Scene scene,
SceneGroup group) {
return new WorldChallenge(
scene, group,
scene,
group,
challengeId, // Id
challengeIndex, // Index
List.of(timeLimit, triggerCount),
timeLimit, // Limit
triggerCount, // Goal
List.of(new InTimeTrigger(), new TriggerGroupTriggerTrigger(Integer.toString(triggerTag)))
);
triggerCount, // Goal
List.of(new InTimeTrigger(), new TriggerGroupTriggerTrigger(Integer.toString(triggerTag))));
}
}

View File

@@ -1,16 +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,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.entity.EntityMonster;
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,22 +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,370 +1,370 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.GameConstants;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.avatar.AvatarData;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.inventory.EquipType;
import emu.grasscutter.game.inventory.GameItem;
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.AbilityControlBlockOuterClass.AbilityControlBlock;
import emu.grasscutter.net.proto.AbilityEmbryoOuterClass.AbilityEmbryo;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.ChangeEnergyReasonOuterClass.ChangeEnergyReason;
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason;
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.PlayerDieTypeOuterClass.PlayerDieType;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneAvatarInfoOuterClass.SceneAvatarInfo;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.event.player.PlayerMoveEvent;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import lombok.Getter;
import lombok.val;
public class EntityAvatar extends GameEntity {
@Getter private final Avatar avatar;
@Getter private PlayerDieType killedType;
@Getter private int killedBy;
public EntityAvatar(Avatar avatar) {
this(null, avatar);
}
public EntityAvatar(Scene scene, Avatar avatar) {
super(scene);
this.avatar = avatar;
this.avatar.setCurrentEnergy();
if (getScene() != null) {
this.id = getScene().getWorld().getNextEntityId(EntityIdType.AVATAR);
var weapon = getAvatar().getWeapon();
if (weapon != null) {
weapon.setWeaponEntityId(getScene().getWorld().getNextEntityId(EntityIdType.WEAPON));
}
}
}
@Override
public int getEntityTypeId() {
return this.getAvatar().getAvatarId();
}
public Player getPlayer() {
return this.avatar.getPlayer();
}
@Override
public Position getPosition() {
return getPlayer().getPosition();
}
@Override
public Position getRotation() {
return getPlayer().getRotation();
}
@Override
public boolean isAlive() {
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
}
@Override
public Int2FloatMap getFightProperties() {
return getAvatar().getFightProperties();
}
public int getWeaponEntityId() {
if (getAvatar().getWeapon() != null) {
return getAvatar().getWeapon().getWeaponEntityId();
}
return 0;
}
@Override
public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
this.killedType = PlayerDieType.PLAYER_DIE_TYPE_KILL_BY_MONSTER;
this.killedBy = killerId;
clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE);
}
public void onDeath(PlayerDieType dieType, int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
this.killedType = dieType;
this.killedBy = killerId;
clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE);
}
@Override
public float heal(float amount) {
// Do not heal character if they are dead
if (!this.isAlive()) {
return 0f;
}
float healed = super.heal(amount);
if (healed > 0f) {
getScene()
.broadcastPacket(
new PacketEntityFightPropChangeReasonNotify(
this,
FightProperty.FIGHT_PROP_CUR_HP,
healed,
PropChangeReason.PROP_CHANGE_REASON_ABILITY,
ChangeHpReason.CHANGE_HP_REASON_ADD_ABILITY));
}
return healed;
}
public void clearEnergy(ChangeEnergyReason reason) {
// Fight props.
val curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp();
float curEnergy = this.getFightProperty(curEnergyProp);
// Set energy to zero.
this.avatar.setCurrentEnergy(curEnergyProp, 0);
// Send packets.
this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp));
if (reason == ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START) {
this.getScene()
.broadcastPacket(
new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, -curEnergy, reason));
}
}
/**
* Adds a fixed amount of energy to the current avatar.
*
* @param amount The amount of energy to add.
* @return True if the energy was added, false if the energy was not added.
*/
public boolean addEnergy(float amount) {
var curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp();
var curEnergy = this.getFightProperty(curEnergyProp);
if (curEnergy == amount) return false;
this.getAvatar().setCurrentEnergy(curEnergyProp, amount);
this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp));
return true;
}
public void addEnergy(float amount, PropChangeReason reason) {
this.addEnergy(amount, reason, false);
}
public void addEnergy(float amount, PropChangeReason reason, boolean isFlat) {
// Get current and maximum energy for this avatar.
val elementType = this.getAvatar().getSkillDepot().getElementType();
val curEnergyProp = elementType.getCurEnergyProp();
val maxEnergyProp = elementType.getMaxEnergyProp();
float curEnergy = this.getFightProperty(curEnergyProp);
float maxEnergy = this.getFightProperty(maxEnergyProp);
// Scale amount by energy recharge, if the amount is not flat.
if (!isFlat) {
amount *= this.getFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY);
}
// Determine the new energy value.
float newEnergy = Math.min(curEnergy + amount, maxEnergy);
// Set energy and notify.
if (newEnergy != curEnergy) {
this.avatar.setCurrentEnergy(curEnergyProp, newEnergy);
this.getScene()
.broadcastPacket(new PacketAvatarFightPropUpdateNotify(this.getAvatar(), curEnergyProp));
this.getScene()
.broadcastPacket(
new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, newEnergy, reason));
}
}
public SceneAvatarInfo getSceneAvatarInfo() {
val avatar = this.getAvatar();
val player = this.getPlayer();
SceneAvatarInfo.Builder avatarInfo =
SceneAvatarInfo.newBuilder()
.setUid(player.getUid())
.setAvatarId(avatar.getAvatarId())
.setGuid(avatar.getGuid())
.setPeerId(player.getPeerId())
.addAllTalentIdList(avatar.getTalentIdList())
.setCoreProudSkillLevel(avatar.getCoreProudSkillLevel())
.putAllSkillLevelMap(avatar.getSkillLevelMap())
.setSkillDepotId(avatar.getSkillDepotId())
.addAllInherentProudSkillList(avatar.getProudSkillList())
.putAllProudSkillExtraLevelMap(avatar.getProudSkillBonusMap())
.addAllTeamResonanceList(player.getTeamManager().getTeamResonances())
.setWearingFlycloakId(avatar.getFlyCloak())
.setCostumeId(avatar.getCostume())
.setBornTime(avatar.getBornTime());
for (GameItem item : avatar.getEquips().values()) {
if (item.getItemData().getEquipType() == EquipType.EQUIP_WEAPON) {
avatarInfo.setWeapon(item.createSceneWeaponInfo());
} else {
avatarInfo.addReliquaryList(item.createSceneReliquaryInfo());
}
avatarInfo.addEquipIdList(item.getItemId());
}
return avatarInfo.build();
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority =
EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo =
SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_AVATAR)
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLastMoveSceneTimeMs(this.getLastMoveSceneTimeMs())
.setLastMoveReliableSeq(this.getLastMoveReliableSeq())
.setLifeState(this.getLifeState().getValue());
if (this.getScene() != null) {
entityInfo.setMotionInfo(this.getMotionInfo());
}
this.addAllFightPropsToEntityInfo(entityInfo);
PropPair pair =
PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(
ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getAvatar().getLevel()))
.build();
entityInfo.addPropList(pair);
entityInfo.setAvatar(this.getSceneAvatarInfo());
return entityInfo.build();
}
public AbilityControlBlock getAbilityControlBlock() {
AvatarData data = this.getAvatar().getAvatarData();
AbilityControlBlock.Builder abilityControlBlock = AbilityControlBlock.newBuilder();
int embryoId = 0;
// Add avatar abilities
if (data.getAbilities() != null) {
for (int id : data.getAbilities()) {
AbilityEmbryo emb =
AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(id)
.setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME)
.build();
abilityControlBlock.addAbilityEmbryoList(emb);
}
}
// Add default abilities
for (int id : GameConstants.DEFAULT_ABILITY_HASHES) {
AbilityEmbryo emb =
AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(id)
.setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME)
.build();
abilityControlBlock.addAbilityEmbryoList(emb);
}
// Add team resonances
for (int id : this.getPlayer().getTeamManager().getTeamResonancesConfig()) {
AbilityEmbryo emb =
AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(id)
.setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME)
.build();
abilityControlBlock.addAbilityEmbryoList(emb);
}
// Add skill depot abilities
AvatarSkillDepotData skillDepot =
GameData.getAvatarSkillDepotDataMap().get(this.getAvatar().getSkillDepotId());
if (skillDepot != null && skillDepot.getAbilities() != null) {
for (int id : skillDepot.getAbilities()) {
AbilityEmbryo emb =
AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(id)
.setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME)
.build();
abilityControlBlock.addAbilityEmbryoList(emb);
}
}
// Add equip abilities
if (this.getAvatar().getExtraAbilityEmbryos().size() > 0) {
for (String skill : this.getAvatar().getExtraAbilityEmbryos()) {
AbilityEmbryo emb =
AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(Utils.abilityHash(skill))
.setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME)
.build();
abilityControlBlock.addAbilityEmbryoList(emb);
}
}
//
return abilityControlBlock.build();
}
/**
* Move this entity to a new position. Additionally invoke player move event.
*
* @param newPosition The new position.
* @param rotation The new rotation.
*/
@Override
public void move(Position newPosition, Position rotation) {
// Invoke player move event.
PlayerMoveEvent event =
new PlayerMoveEvent(
this.getPlayer(), PlayerMoveEvent.MoveType.PLAYER, this.getPosition(), newPosition);
event.call();
// Set position and rotation.
super.move(event.getDestination(), rotation);
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.GameConstants;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.avatar.AvatarData;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.inventory.EquipType;
import emu.grasscutter.game.inventory.GameItem;
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.AbilityControlBlockOuterClass.AbilityControlBlock;
import emu.grasscutter.net.proto.AbilityEmbryoOuterClass.AbilityEmbryo;
import emu.grasscutter.net.proto.AbilitySyncStateInfoOuterClass.AbilitySyncStateInfo;
import emu.grasscutter.net.proto.AnimatorParameterValueInfoPairOuterClass.AnimatorParameterValueInfoPair;
import emu.grasscutter.net.proto.ChangeEnergyReasonOuterClass.ChangeEnergyReason;
import emu.grasscutter.net.proto.ChangeHpReasonOuterClass.ChangeHpReason;
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.PlayerDieTypeOuterClass.PlayerDieType;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.net.proto.PropPairOuterClass.PropPair;
import emu.grasscutter.net.proto.ProtEntityTypeOuterClass.ProtEntityType;
import emu.grasscutter.net.proto.SceneAvatarInfoOuterClass.SceneAvatarInfo;
import emu.grasscutter.net.proto.SceneEntityAiInfoOuterClass.SceneEntityAiInfo;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.server.event.player.PlayerMoveEvent;
import emu.grasscutter.server.packet.send.PacketAvatarFightPropUpdateNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropChangeReasonNotify;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.utils.Position;
import emu.grasscutter.utils.ProtoHelper;
import emu.grasscutter.utils.Utils;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import lombok.Getter;
import lombok.val;
public class EntityAvatar extends GameEntity {
@Getter private final Avatar avatar;
@Getter private PlayerDieType killedType;
@Getter private int killedBy;
public EntityAvatar(Avatar avatar) {
this(null, avatar);
}
public EntityAvatar(Scene scene, Avatar avatar) {
super(scene);
this.avatar = avatar;
this.avatar.setCurrentEnergy();
if (getScene() != null) {
this.id = getScene().getWorld().getNextEntityId(EntityIdType.AVATAR);
var weapon = getAvatar().getWeapon();
if (weapon != null) {
weapon.setWeaponEntityId(getScene().getWorld().getNextEntityId(EntityIdType.WEAPON));
}
}
}
@Override
public int getEntityTypeId() {
return this.getAvatar().getAvatarId();
}
public Player getPlayer() {
return this.avatar.getPlayer();
}
@Override
public Position getPosition() {
return getPlayer().getPosition();
}
@Override
public Position getRotation() {
return getPlayer().getRotation();
}
@Override
public boolean isAlive() {
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
}
@Override
public Int2FloatMap getFightProperties() {
return getAvatar().getFightProperties();
}
public int getWeaponEntityId() {
if (getAvatar().getWeapon() != null) {
return getAvatar().getWeapon().getWeaponEntityId();
}
return 0;
}
@Override
public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
this.killedType = PlayerDieType.PLAYER_DIE_TYPE_KILL_BY_MONSTER;
this.killedBy = killerId;
clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE);
}
public void onDeath(PlayerDieType dieType, int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
this.killedType = dieType;
this.killedBy = killerId;
clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_NONE);
}
@Override
public float heal(float amount) {
// Do not heal character if they are dead
if (!this.isAlive()) {
return 0f;
}
float healed = super.heal(amount);
if (healed > 0f) {
getScene()
.broadcastPacket(
new PacketEntityFightPropChangeReasonNotify(
this,
FightProperty.FIGHT_PROP_CUR_HP,
healed,
PropChangeReason.PROP_CHANGE_REASON_ABILITY,
ChangeHpReason.CHANGE_HP_REASON_ADD_ABILITY));
}
return healed;
}
public void clearEnergy(ChangeEnergyReason reason) {
// Fight props.
val curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp();
float curEnergy = this.getFightProperty(curEnergyProp);
// Set energy to zero.
this.avatar.setCurrentEnergy(curEnergyProp, 0);
// Send packets.
this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp));
if (reason == ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START) {
this.getScene()
.broadcastPacket(
new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, -curEnergy, reason));
}
}
/**
* Adds a fixed amount of energy to the current avatar.
*
* @param amount The amount of energy to add.
* @return True if the energy was added, false if the energy was not added.
*/
public boolean addEnergy(float amount) {
var curEnergyProp = this.getAvatar().getSkillDepot().getElementType().getCurEnergyProp();
var curEnergy = this.getFightProperty(curEnergyProp);
if (curEnergy == amount) return false;
this.getAvatar().setCurrentEnergy(curEnergyProp, amount);
this.getScene().broadcastPacket(new PacketEntityFightPropUpdateNotify(this, curEnergyProp));
return true;
}
public void addEnergy(float amount, PropChangeReason reason) {
this.addEnergy(amount, reason, false);
}
public void addEnergy(float amount, PropChangeReason reason, boolean isFlat) {
// Get current and maximum energy for this avatar.
val elementType = this.getAvatar().getSkillDepot().getElementType();
val curEnergyProp = elementType.getCurEnergyProp();
val maxEnergyProp = elementType.getMaxEnergyProp();
float curEnergy = this.getFightProperty(curEnergyProp);
float maxEnergy = this.getFightProperty(maxEnergyProp);
// Scale amount by energy recharge, if the amount is not flat.
if (!isFlat) {
amount *= this.getFightProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY);
}
// Determine the new energy value.
float newEnergy = Math.min(curEnergy + amount, maxEnergy);
// Set energy and notify.
if (newEnergy != curEnergy) {
this.avatar.setCurrentEnergy(curEnergyProp, newEnergy);
this.getScene()
.broadcastPacket(new PacketAvatarFightPropUpdateNotify(this.getAvatar(), curEnergyProp));
this.getScene()
.broadcastPacket(
new PacketEntityFightPropChangeReasonNotify(this, curEnergyProp, newEnergy, reason));
}
}
public SceneAvatarInfo getSceneAvatarInfo() {
val avatar = this.getAvatar();
val player = this.getPlayer();
SceneAvatarInfo.Builder avatarInfo =
SceneAvatarInfo.newBuilder()
.setUid(player.getUid())
.setAvatarId(avatar.getAvatarId())
.setGuid(avatar.getGuid())
.setPeerId(player.getPeerId())
.addAllTalentIdList(avatar.getTalentIdList())
.setCoreProudSkillLevel(avatar.getCoreProudSkillLevel())
.putAllSkillLevelMap(avatar.getSkillLevelMap())
.setSkillDepotId(avatar.getSkillDepotId())
.addAllInherentProudSkillList(avatar.getProudSkillList())
.putAllProudSkillExtraLevelMap(avatar.getProudSkillBonusMap())
.addAllTeamResonanceList(player.getTeamManager().getTeamResonances())
.setWearingFlycloakId(avatar.getFlyCloak())
.setCostumeId(avatar.getCostume())
.setBornTime(avatar.getBornTime());
for (GameItem item : avatar.getEquips().values()) {
if (item.getItemData().getEquipType() == EquipType.EQUIP_WEAPON) {
avatarInfo.setWeapon(item.createSceneWeaponInfo());
} else {
avatarInfo.addReliquaryList(item.createSceneReliquaryInfo());
}
avatarInfo.addEquipIdList(item.getItemId());
}
return avatarInfo.build();
}
@Override
public SceneEntityInfo toProto() {
EntityAuthorityInfo authority =
EntityAuthorityInfo.newBuilder()
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
.setAiInfo(
SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(Vector.newBuilder()))
.setBornPos(Vector.newBuilder())
.build();
SceneEntityInfo.Builder entityInfo =
SceneEntityInfo.newBuilder()
.setEntityId(getId())
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_AVATAR)
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
.setEntityClientData(EntityClientData.newBuilder())
.setEntityAuthorityInfo(authority)
.setLastMoveSceneTimeMs(this.getLastMoveSceneTimeMs())
.setLastMoveReliableSeq(this.getLastMoveReliableSeq())
.setLifeState(this.getLifeState().getValue());
if (this.getScene() != null) {
entityInfo.setMotionInfo(this.getMotionInfo());
}
this.addAllFightPropsToEntityInfo(entityInfo);
PropPair pair =
PropPair.newBuilder()
.setType(PlayerProperty.PROP_LEVEL.getId())
.setPropValue(
ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getAvatar().getLevel()))
.build();
entityInfo.addPropList(pair);
entityInfo.setAvatar(this.getSceneAvatarInfo());
return entityInfo.build();
}
public AbilityControlBlock getAbilityControlBlock() {
AvatarData data = this.getAvatar().getAvatarData();
AbilityControlBlock.Builder abilityControlBlock = AbilityControlBlock.newBuilder();
int embryoId = 0;
// Add avatar abilities
if (data.getAbilities() != null) {
for (int id : data.getAbilities()) {
AbilityEmbryo emb =
AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(id)
.setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME)
.build();
abilityControlBlock.addAbilityEmbryoList(emb);
}
}
// Add default abilities
for (int id : GameConstants.DEFAULT_ABILITY_HASHES) {
AbilityEmbryo emb =
AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(id)
.setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME)
.build();
abilityControlBlock.addAbilityEmbryoList(emb);
}
// Add team resonances
for (int id : this.getPlayer().getTeamManager().getTeamResonancesConfig()) {
AbilityEmbryo emb =
AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(id)
.setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME)
.build();
abilityControlBlock.addAbilityEmbryoList(emb);
}
// Add skill depot abilities
AvatarSkillDepotData skillDepot =
GameData.getAvatarSkillDepotDataMap().get(this.getAvatar().getSkillDepotId());
if (skillDepot != null && skillDepot.getAbilities() != null) {
for (int id : skillDepot.getAbilities()) {
AbilityEmbryo emb =
AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(id)
.setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME)
.build();
abilityControlBlock.addAbilityEmbryoList(emb);
}
}
// Add equip abilities
if (this.getAvatar().getExtraAbilityEmbryos().size() > 0) {
for (String skill : this.getAvatar().getExtraAbilityEmbryos()) {
AbilityEmbryo emb =
AbilityEmbryo.newBuilder()
.setAbilityId(++embryoId)
.setAbilityNameHash(Utils.abilityHash(skill))
.setAbilityOverrideNameHash(GameConstants.DEFAULT_ABILITY_NAME)
.build();
abilityControlBlock.addAbilityEmbryoList(emb);
}
}
//
return abilityControlBlock.build();
}
/**
* Move this entity to a new position. Additionally invoke player move event.
*
* @param newPosition The new position.
* @param rotation The new rotation.
*/
@Override
public void move(Position newPosition, Position rotation) {
// Invoke player move event.
PlayerMoveEvent event =
new PlayerMoveEvent(
this.getPlayer(), PlayerMoveEvent.MoveType.PLAYER, this.getPosition(), newPosition);
event.call();
// Set position and rotation.
super.move(event.getDestination(), rotation);
}
}

View File

@@ -1,63 +1,63 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.data.binout.config.ConfigEntityGadget;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.utils.Position;
import lombok.Getter;
public abstract class EntityBaseGadget extends GameEntity {
@Getter(onMethod_ = @Override)
protected final Position position;
@Getter(onMethod_ = @Override)
protected final Position rotation;
public EntityBaseGadget(Scene scene) {
this(scene, null, null);
}
public EntityBaseGadget(Scene scene, Position position, Position rotation) {
super(scene);
this.position = position != null ? position.clone() : new Position();
this.rotation = rotation != null ? rotation.clone() : new Position();
}
public abstract int getGadgetId();
@Override
public int getEntityTypeId() {
return this.getGadgetId();
}
@Override
public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
}
protected void fillFightProps(ConfigEntityGadget configGadget) {
if (configGadget == null || configGadget.getCombat() == null) {
return;
}
var combatData = configGadget.getCombat();
var combatProperties = combatData.getProperty();
var targetHp = combatProperties.getHP();
setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, targetHp);
setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, targetHp);
if (combatProperties.isInvincible()) {
targetHp = Float.POSITIVE_INFINITY;
}
setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, targetHp);
var atk = combatProperties.getAttack();
setFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, atk);
setFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK, atk);
var def = combatProperties.getDefence();
setFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, def);
setFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE, def);
setLockHP(combatProperties.isLockHP());
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.data.binout.config.ConfigEntityGadget;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.utils.Position;
import lombok.Getter;
public abstract class EntityBaseGadget extends GameEntity {
@Getter(onMethod_ = @Override)
protected final Position position;
@Getter(onMethod_ = @Override)
protected final Position rotation;
public EntityBaseGadget(Scene scene) {
this(scene, null, null);
}
public EntityBaseGadget(Scene scene, Position position, Position rotation) {
super(scene);
this.position = position != null ? position.clone() : new Position();
this.rotation = rotation != null ? rotation.clone() : new Position();
}
public abstract int getGadgetId();
@Override
public int getEntityTypeId() {
return this.getGadgetId();
}
@Override
public void onDeath(int killerId) {
super.onDeath(killerId); // Invoke super class's onDeath() method.
}
protected void fillFightProps(ConfigEntityGadget configGadget) {
if (configGadget == null || configGadget.getCombat() == null) {
return;
}
var combatData = configGadget.getCombat();
var combatProperties = combatData.getProperty();
var targetHp = combatProperties.getHP();
setFightProperty(FightProperty.FIGHT_PROP_MAX_HP, targetHp);
setFightProperty(FightProperty.FIGHT_PROP_BASE_HP, targetHp);
if (combatProperties.isInvincible()) {
targetHp = Float.POSITIVE_INFINITY;
}
setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, targetHp);
var atk = combatProperties.getAttack();
setFightProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, atk);
setFightProperty(FightProperty.FIGHT_PROP_CUR_ATTACK, atk);
var def = combatProperties.getDefence();
setFightProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, def);
setFightProperty(FightProperty.FIGHT_PROP_CUR_DEFENSE, def);
setLockHP(combatProperties.isLockHP());
}
}

View File

@@ -1,281 +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 lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
@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,96 +1,96 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass;
import emu.grasscutter.scripts.data.SceneRegion;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import lombok.Getter;
@Getter
public class EntityRegion extends GameEntity {
private final Position position;
private final Set<Integer> entities; // Ids of entities inside this region
private final SceneRegion metaRegion;
private boolean hasNewEntities;
private boolean entityLeave;
public EntityRegion(Scene scene, SceneRegion region) {
super(scene);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.REGION);
this.setGroupId(region.group.id);
this.setBlockId(region.group.block_id);
this.setConfigId(region.config_id);
this.position = region.pos.clone();
this.entities = ConcurrentHashMap.newKeySet();
this.metaRegion = region;
}
@Override
public int getEntityTypeId() {
return this.metaRegion.config_id;
}
public void addEntity(GameEntity entity) {
if (this.getEntities().contains(entity.getId())) {
return;
}
this.getEntities().add(entity.getId());
this.hasNewEntities = true;
}
public boolean hasNewEntities() {
return hasNewEntities;
}
public void resetNewEntities() {
hasNewEntities = false;
}
public void removeEntity(int entityId) {
this.getEntities().remove(entityId);
this.entityLeave = true;
}
public void removeEntity(GameEntity entity) {
this.getEntities().remove(entity.getId());
this.entityLeave = true;
}
public boolean entityLeave() {
return this.entityLeave;
}
public void resetEntityLeave() {
this.entityLeave = false;
}
@Override
public Int2FloatMap getFightProperties() {
return null;
}
@Override
public Position getPosition() {
return position;
}
@Override
public Position getRotation() {
return null;
}
@Override
public SceneEntityInfoOuterClass.SceneEntityInfo toProto() {
/** The Region Entity would not be sent to client. */
return null;
}
public int getFirstEntityId() {
return entities.stream().findFirst().orElse(0);
}
}
package emu.grasscutter.game.entity;
import emu.grasscutter.game.props.EntityIdType;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass;
import emu.grasscutter.scripts.data.SceneRegion;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import lombok.Getter;
@Getter
public class EntityRegion extends GameEntity {
private final Position position;
private final Set<Integer> entities; // Ids of entities inside this region
private final SceneRegion metaRegion;
private boolean hasNewEntities;
private boolean entityLeave;
public EntityRegion(Scene scene, SceneRegion region) {
super(scene);
this.id = getScene().getWorld().getNextEntityId(EntityIdType.REGION);
this.setGroupId(region.group.id);
this.setBlockId(region.group.block_id);
this.setConfigId(region.config_id);
this.position = region.pos.clone();
this.entities = ConcurrentHashMap.newKeySet();
this.metaRegion = region;
}
@Override
public int getEntityTypeId() {
return this.metaRegion.config_id;
}
public void addEntity(GameEntity entity) {
if (this.getEntities().contains(entity.getId())) {
return;
}
this.getEntities().add(entity.getId());
this.hasNewEntities = true;
}
public boolean hasNewEntities() {
return hasNewEntities;
}
public void resetNewEntities() {
hasNewEntities = false;
}
public void removeEntity(int entityId) {
this.getEntities().remove(entityId);
this.entityLeave = true;
}
public void removeEntity(GameEntity entity) {
this.getEntities().remove(entity.getId());
this.entityLeave = true;
}
public boolean entityLeave() {
return this.entityLeave;
}
public void resetEntityLeave() {
this.entityLeave = false;
}
@Override
public Int2FloatMap getFightProperties() {
return null;
}
@Override
public Position getPosition() {
return position;
}
@Override
public Position getRotation() {
return null;
}
@Override
public SceneEntityInfoOuterClass.SceneEntityInfo toProto() {
/** The Region Entity would not be sent to client. */
return null;
}
public int getFirstEntityId() {
return entities.stream().findFirst().orElse(0);
}
}

View File

@@ -1,30 +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,112 +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 lombok.Getter;
import lombok.Setter;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
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,264 +1,264 @@
package emu.grasscutter.game.entity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.SpawnDataEntry;
import emu.grasscutter.game.world.World;
import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.scripts.data.controller.EntityController;
import emu.grasscutter.server.event.entity.EntityDamageEvent;
import emu.grasscutter.server.event.entity.EntityDeathEvent;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2FloatMap;
import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap;
import lombok.Getter;
import lombok.Setter;
public abstract class GameEntity {
@Getter private final Scene scene;
@Getter protected int id;
@Getter @Setter private SpawnDataEntry spawnEntry;
@Getter @Setter private int blockId;
@Getter @Setter private int configId;
@Getter @Setter private int groupId;
@Getter @Setter private MotionState motionState;
@Getter @Setter private int lastMoveSceneTimeMs;
@Getter @Setter private int lastMoveReliableSeq;
@Getter @Setter private boolean lockHP;
// Lua controller for specific actions
@Getter @Setter private EntityController entityController;
@Getter private ElementType lastAttackType = ElementType.None;
// Abilities
private Object2FloatMap<String> metaOverrideMap;
private Int2ObjectMap<String> metaModifiers;
public GameEntity(Scene scene) {
this.scene = scene;
this.motionState = MotionState.MOTION_STATE_NONE;
}
public int getEntityType() {
return this.getId() >> 24;
}
public abstract int getEntityTypeId();
public World getWorld() {
return this.getScene().getWorld();
}
public boolean isAlive() {
return true;
}
public LifeState getLifeState() {
return this.isAlive() ? LifeState.LIFE_ALIVE : LifeState.LIFE_DEAD;
}
public Object2FloatMap<String> getMetaOverrideMap() {
if (this.metaOverrideMap == null) {
this.metaOverrideMap = new Object2FloatOpenHashMap<>();
}
return this.metaOverrideMap;
}
public Int2ObjectMap<String> getMetaModifiers() {
if (this.metaModifiers == null) {
this.metaModifiers = new Int2ObjectOpenHashMap<>();
}
return this.metaModifiers;
}
public abstract Int2FloatMap getFightProperties();
public abstract Position getPosition();
public abstract Position getRotation();
public void setFightProperty(FightProperty prop, float value) {
this.getFightProperties().put(prop.getId(), value);
}
public void setFightProperty(int id, float value) {
this.getFightProperties().put(id, value);
}
public void addFightProperty(FightProperty prop, float value) {
this.getFightProperties().put(prop.getId(), this.getFightProperty(prop) + value);
}
public float getFightProperty(FightProperty prop) {
return this.getFightProperties().getOrDefault(prop.getId(), 0f);
}
public boolean hasFightProperty(FightProperty prop) {
return this.getFightProperties().containsKey(prop.getId());
}
public void addAllFightPropsToEntityInfo(SceneEntityInfo.Builder entityInfo) {
this.getFightProperties()
.forEach(
(key, value) -> {
if (key == 0) return;
entityInfo.addFightPropList(
FightPropPair.newBuilder().setPropType(key).setPropValue(value).build());
});
}
protected MotionInfo getMotionInfo() {
return MotionInfo.newBuilder()
.setPos(this.getPosition().toProto())
.setRot(this.getRotation().toProto())
.setSpeed(Vector.newBuilder())
.setState(this.getMotionState())
.build();
}
public float heal(float amount) {
if (this.getFightProperties() == null) {
return 0f;
}
float curHp = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
float maxHp = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
if (curHp >= maxHp) {
return 0f;
}
float healed = Math.min(maxHp - curHp, amount);
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, healed);
this.getScene()
.broadcastPacket(
new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP));
return healed;
}
public void damage(float amount) {
this.damage(amount, 0, ElementType.None);
}
public void damage(float amount, int killerId, ElementType attackType) {
// Check if the entity has properties.
if (this.getFightProperties() == null || !hasFightProperty(FightProperty.FIGHT_PROP_CUR_HP)) {
return;
}
// Invoke entity damage event.
EntityDamageEvent event =
new EntityDamageEvent(this, amount, attackType, this.getScene().getEntityById(killerId));
event.call();
if (event.isCanceled()) {
return; // If the event is canceled, do not damage the entity.
}
float curHp = getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
if (curHp != Float.POSITIVE_INFINITY && !lockHP || lockHP && curHp <= event.getDamage()) {
// Add negative HP to the current HP property.
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -(event.getDamage()));
}
// Check if dead
boolean isDead = false;
if (this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) {
this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
isDead = true;
}
// Packets
this.getScene()
.broadcastPacket(
new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP));
// Check if dead.
if (isDead) {
this.getScene().killEntity(this, killerId);
}
}
/**
* Runs the Lua callbacks for {@link EntityDamageEvent}.
*
* @param event The damage event.
*/
public void runLuaCallbacks(EntityDamageEvent event) {
if (entityController != null) {
entityController.onBeHurt(this, event.getAttackElementType(), true);//todo is host handling
}
}
/**
* Move this entity to a new position.
*
* @param position The new position.
* @param rotation The new rotation.
*/
public void move(Position position, Position rotation) {
// Set the position and rotation.
this.getPosition().set(position);
this.getRotation().set(rotation);
}
/**
* Called when a player interacts with this entity
*
* @param player Player that is interacting with this entity
* @param interactReq Interact request protobuf data
*/
public void onInteract(Player player, GadgetInteractReq interactReq) {}
/** Called when this entity is added to the world */
public void onCreate() {}
public void onRemoved() {}
public void onTick(int sceneTime) {
if (entityController != null) {
entityController.onTimer(this, sceneTime);
}
}
public int onClientExecuteRequest(int param1, int param2, int param3) {
if (entityController != null) {
return entityController.onClientExecuteRequest(this, param1, param2, param3);
}
return 0;
}
/**
* Called when this entity dies
*
* @param killerId Entity id of the entity that killed this entity
*/
public void onDeath(int killerId) {
// Invoke entity death event.
EntityDeathEvent event = new EntityDeathEvent(this, killerId);
event.call();
// Run Lua callbacks.
if (entityController != null) {
entityController.onDie(this, getLastAttackType());
}
}
public abstract SceneEntityInfo toProto();
}
package emu.grasscutter.game.entity;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.LifeState;
import emu.grasscutter.game.world.Scene;
import emu.grasscutter.game.world.SpawnDataEntry;
import emu.grasscutter.game.world.World;
import emu.grasscutter.net.proto.FightPropPairOuterClass.FightPropPair;
import emu.grasscutter.net.proto.GadgetInteractReqOuterClass.GadgetInteractReq;
import emu.grasscutter.net.proto.MotionInfoOuterClass.MotionInfo;
import emu.grasscutter.net.proto.MotionStateOuterClass.MotionState;
import emu.grasscutter.net.proto.SceneEntityInfoOuterClass.SceneEntityInfo;
import emu.grasscutter.net.proto.VectorOuterClass.Vector;
import emu.grasscutter.scripts.data.controller.EntityController;
import emu.grasscutter.server.event.entity.EntityDamageEvent;
import emu.grasscutter.server.event.entity.EntityDeathEvent;
import emu.grasscutter.server.packet.send.PacketEntityFightPropUpdateNotify;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2FloatMap;
import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap;
import lombok.Getter;
import lombok.Setter;
public abstract class GameEntity {
@Getter private final Scene scene;
@Getter protected int id;
@Getter @Setter private SpawnDataEntry spawnEntry;
@Getter @Setter private int blockId;
@Getter @Setter private int configId;
@Getter @Setter private int groupId;
@Getter @Setter private MotionState motionState;
@Getter @Setter private int lastMoveSceneTimeMs;
@Getter @Setter private int lastMoveReliableSeq;
@Getter @Setter private boolean lockHP;
// Lua controller for specific actions
@Getter @Setter private EntityController entityController;
@Getter private ElementType lastAttackType = ElementType.None;
// Abilities
private Object2FloatMap<String> metaOverrideMap;
private Int2ObjectMap<String> metaModifiers;
public GameEntity(Scene scene) {
this.scene = scene;
this.motionState = MotionState.MOTION_STATE_NONE;
}
public int getEntityType() {
return this.getId() >> 24;
}
public abstract int getEntityTypeId();
public World getWorld() {
return this.getScene().getWorld();
}
public boolean isAlive() {
return true;
}
public LifeState getLifeState() {
return this.isAlive() ? LifeState.LIFE_ALIVE : LifeState.LIFE_DEAD;
}
public Object2FloatMap<String> getMetaOverrideMap() {
if (this.metaOverrideMap == null) {
this.metaOverrideMap = new Object2FloatOpenHashMap<>();
}
return this.metaOverrideMap;
}
public Int2ObjectMap<String> getMetaModifiers() {
if (this.metaModifiers == null) {
this.metaModifiers = new Int2ObjectOpenHashMap<>();
}
return this.metaModifiers;
}
public abstract Int2FloatMap getFightProperties();
public abstract Position getPosition();
public abstract Position getRotation();
public void setFightProperty(FightProperty prop, float value) {
this.getFightProperties().put(prop.getId(), value);
}
public void setFightProperty(int id, float value) {
this.getFightProperties().put(id, value);
}
public void addFightProperty(FightProperty prop, float value) {
this.getFightProperties().put(prop.getId(), this.getFightProperty(prop) + value);
}
public float getFightProperty(FightProperty prop) {
return this.getFightProperties().getOrDefault(prop.getId(), 0f);
}
public boolean hasFightProperty(FightProperty prop) {
return this.getFightProperties().containsKey(prop.getId());
}
public void addAllFightPropsToEntityInfo(SceneEntityInfo.Builder entityInfo) {
this.getFightProperties()
.forEach(
(key, value) -> {
if (key == 0) return;
entityInfo.addFightPropList(
FightPropPair.newBuilder().setPropType(key).setPropValue(value).build());
});
}
protected MotionInfo getMotionInfo() {
return MotionInfo.newBuilder()
.setPos(this.getPosition().toProto())
.setRot(this.getRotation().toProto())
.setSpeed(Vector.newBuilder())
.setState(this.getMotionState())
.build();
}
public float heal(float amount) {
if (this.getFightProperties() == null) {
return 0f;
}
float curHp = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
float maxHp = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
if (curHp >= maxHp) {
return 0f;
}
float healed = Math.min(maxHp - curHp, amount);
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, healed);
this.getScene()
.broadcastPacket(
new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP));
return healed;
}
public void damage(float amount) {
this.damage(amount, 0, ElementType.None);
}
public void damage(float amount, int killerId, ElementType attackType) {
// Check if the entity has properties.
if (this.getFightProperties() == null || !hasFightProperty(FightProperty.FIGHT_PROP_CUR_HP)) {
return;
}
// Invoke entity damage event.
EntityDamageEvent event =
new EntityDamageEvent(this, amount, attackType, this.getScene().getEntityById(killerId));
event.call();
if (event.isCanceled()) {
return; // If the event is canceled, do not damage the entity.
}
float curHp = getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
if (curHp != Float.POSITIVE_INFINITY && !lockHP || lockHP && curHp <= event.getDamage()) {
// Add negative HP to the current HP property.
this.addFightProperty(FightProperty.FIGHT_PROP_CUR_HP, -(event.getDamage()));
}
// Check if dead
boolean isDead = false;
if (this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) <= 0f) {
this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, 0f);
isDead = true;
}
// Packets
this.getScene()
.broadcastPacket(
new PacketEntityFightPropUpdateNotify(this, FightProperty.FIGHT_PROP_CUR_HP));
// Check if dead.
if (isDead) {
this.getScene().killEntity(this, killerId);
}
}
/**
* Runs the Lua callbacks for {@link EntityDamageEvent}.
*
* @param event The damage event.
*/
public void runLuaCallbacks(EntityDamageEvent event) {
if (entityController != null) {
entityController.onBeHurt(this, event.getAttackElementType(), true); // todo is host handling
}
}
/**
* Move this entity to a new position.
*
* @param position The new position.
* @param rotation The new rotation.
*/
public void move(Position position, Position rotation) {
// Set the position and rotation.
this.getPosition().set(position);
this.getRotation().set(rotation);
}
/**
* Called when a player interacts with this entity
*
* @param player Player that is interacting with this entity
* @param interactReq Interact request protobuf data
*/
public void onInteract(Player player, GadgetInteractReq interactReq) {}
/** Called when this entity is added to the world */
public void onCreate() {}
public void onRemoved() {}
public void onTick(int sceneTime) {
if (entityController != null) {
entityController.onTimer(this, sceneTime);
}
}
public int onClientExecuteRequest(int param1, int param2, int param3) {
if (entityController != null) {
return entityController.onClientExecuteRequest(this, param1, param2, param3);
}
return 0;
}
/**
* Called when this entity dies
*
* @param killerId Entity id of the entity that killed this entity
*/
public void onDeath(int killerId) {
// Invoke entity death event.
EntityDeathEvent event = new EntityDeathEvent(this, killerId);
event.call();
// Run Lua callbacks.
if (entityController != null) {
entityController.onDie(this, getLastAttackType());
}
}
public abstract SceneEntityInfo toProto();
}

View File

@@ -1,33 +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,40 +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,163 +1,173 @@
package emu.grasscutter.game.mail;
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 org.bson.types.ObjectId;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import static emu.grasscutter.net.proto.MailItemOuterClass.MailItem.*;
@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,133 +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

@@ -1,418 +1,420 @@
package emu.grasscutter.game.managers.energy;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
import com.google.protobuf.InvalidProtocolBufferException;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
import emu.grasscutter.data.excels.monster.MonsterData.HpDrops;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.MonsterType;
import emu.grasscutter.game.props.WeaponType;
import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall;
import emu.grasscutter.net.proto.AbilityIdentifierOuterClass.AbilityIdentifier;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.ChangeEnergyReasonOuterClass.ChangeEnergyReason;
import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import lombok.Getter;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
public class EnergyManager extends BasePlayerManager {
private static final Int2ObjectMap<List<EnergyDropInfo>> energyDropData =
new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<List<SkillParticleGenerationInfo>>
skillParticleGenerationData = new Int2ObjectOpenHashMap<>();
private final Object2IntMap<EntityAvatar> avatarNormalProbabilities;
@Getter private boolean energyUsage; // Should energy usage be enabled for this player?
public EnergyManager(Player player) {
super(player);
this.avatarNormalProbabilities = new Object2IntOpenHashMap<>();
this.energyUsage = GAME_OPTIONS.energyUsage;
}
public static void initialize() {
// Read the data we need for monster energy drops.
try {
DataLoader.loadList("EnergyDrop.json", EnergyDropEntry.class)
.forEach(
entry -> {
energyDropData.put(entry.getDropId(), entry.getDropList());
});
Grasscutter.getLogger().debug("Energy drop data successfully loaded.");
} catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load energy drop data.", ex);
}
// Read the data for particle generation from skills
try {
DataLoader.loadList("SkillParticleGeneration.json", SkillParticleGenerationEntry.class)
.forEach(
entry -> {
skillParticleGenerationData.put(entry.getAvatarId(), entry.getAmountList());
});
Grasscutter.getLogger().debug("Skill particle generation data successfully loaded.");
} catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load skill particle generation data data.", ex);
}
}
/** Particle creation for elemental skills. */
private int getBallCountForAvatar(int avatarId) {
// We default to two particles.
int count = 2;
// If we don't have any data for this avatar, stop.
if (!skillParticleGenerationData.containsKey(avatarId)) {
Grasscutter.getLogger().warn("No particle generation data for avatarId {} found.", avatarId);
}
// If we do have data, roll for how many particles we should generate.
else {
int roll = ThreadLocalRandom.current().nextInt(0, 100);
int percentageStack = 0;
for (SkillParticleGenerationInfo info : skillParticleGenerationData.get(avatarId)) {
int chance = info.getChance();
percentageStack += chance;
if (roll < percentageStack) {
count = info.getValue();
break;
}
}
}
// Done.
return count;
}
private int getBallIdForElement(ElementType element) {
// If we have no element, we default to an element-less particle.
if (element == null) {
return 2024;
}
// Otherwise, we determine the particle's ID based on the element.
return switch (element) {
case Fire -> 2017;
case Water -> 2018;
case Grass -> 2019;
case Electric -> 2020;
case Wind -> 2021;
case Ice -> 2022;
case Rock -> 2023;
default -> 2024;
};
}
public void handleGenerateElemBall(AbilityInvokeEntry invoke)
throws InvalidProtocolBufferException {
// ToDo:
// This is also called when a weapon like Favonius Warbow etc. creates energy through its
// passive.
// We are not handling this correctly at the moment.
// Get action info.
AbilityActionGenerateElemBall action =
AbilityActionGenerateElemBall.parseFrom(invoke.getAbilityData());
if (action == null) {
return;
}
// Default to an elementless particle.
int itemId = 2024;
// Generate 2 particles by default.
int amount = 2;
// Try to get the casting avatar from the player's party.
Optional<EntityAvatar> avatarEntity =
this.getCastingAvatarEntityForEnergy(invoke.getEntityId());
// Bug: invokes twice sometimes, Ayato, Keqing
// ToDo: deal with press, hold difference. deal with charge(Beidou, Yunjin)
if (avatarEntity.isPresent()) {
Avatar avatar = avatarEntity.get().getAvatar();
if (avatar != null) {
int avatarId = avatar.getAvatarId();
AvatarSkillDepotData skillDepotData = avatar.getSkillDepot();
// Determine how many particles we need to create for this avatar.
amount = this.getBallCountForAvatar(avatarId);
// Determine the avatar's element, and based on that the ID of the
// particles we have to generate.
if (skillDepotData != null) {
ElementType element = skillDepotData.getElementType();
itemId = this.getBallIdForElement(element);
}
}
}
// Generate the particles.
var pos = new Position(action.getPos());
for (int i = 0; i < amount; i++) {
this.generateElemBall(itemId, pos, 1);
}
}
/**
* Energy generation for NAs/CAs.
*
* @param avatar The avatar.
*/
private void generateEnergyForNormalAndCharged(EntityAvatar avatar) {
// This logic is based on the descriptions given in
// https://genshin-impact.fandom.com/wiki/Energy#Energy_Generated_by_Normal_Attacks
// https://library.keqingmains.com/combat-mechanics/energy#auto-attacking
// Those descriptions are lacking in some information, so this implementation most likely
// does not fully replicate the behavior of the official server. Open questions:
// - Does the probability for a character reset after some time?
// - Does the probability for a character reset when switching them out?
// - Does this really count every individual hit separately?
// Get the avatar's weapon type.
WeaponType weaponType = avatar.getAvatar().getAvatarData().getWeaponType();
// Check if we already have probability data for this avatar. If not, insert it.
if (!this.avatarNormalProbabilities.containsKey(avatar)) {
this.avatarNormalProbabilities.put(avatar, weaponType.getEnergyGainInitialProbability());
}
// Roll for energy.
int currentProbability = this.avatarNormalProbabilities.getInt(avatar);
int roll = ThreadLocalRandom.current().nextInt(0, 100);
// If the player wins the roll, we increase the avatar's energy and reset the probability.
if (roll < currentProbability) {
avatar.addEnergy(1.0f, PropChangeReason.PROP_CHANGE_REASON_ABILITY, true);
this.avatarNormalProbabilities.put(avatar, weaponType.getEnergyGainInitialProbability());
}
// Otherwise, we increase the probability for the next hit.
else {
this.avatarNormalProbabilities.put(
avatar, currentProbability + weaponType.getEnergyGainIncreaseProbability());
}
}
public void handleAttackHit(EvtBeingHitInfo hitInfo) {
// Get the attack result.
AttackResult attackRes = hitInfo.getAttackResult();
// Make sure the attack was performed by the currently active avatar. If not, we ignore the hit.
Optional<EntityAvatar> attackerEntity =
this.getCastingAvatarEntityForEnergy(attackRes.getAttackerId());
if (attackerEntity.isEmpty()
|| this.player.getTeamManager().getCurrentAvatarEntity().getId()
!= attackerEntity.get().getId()) {
return;
}
// Make sure the target is an actual enemy.
GameEntity targetEntity = this.player.getScene().getEntityById(attackRes.getDefenseId());
if (!(targetEntity instanceof EntityMonster targetMonster)) {
return;
}
MonsterType targetType = targetMonster.getMonsterData().getType();
if (targetType != MonsterType.MONSTER_ORDINARY && targetType != MonsterType.MONSTER_BOSS) {
return;
}
// Get the ability that caused this hit.
AbilityIdentifier ability = attackRes.getAbilityIdentifier();
// Make sure there is no actual "ability" associated with the hit. For now, this is how we
// identify normal and charged attacks. Note that this is not completely accurate:
// - Many character's charged attacks have an ability associated with them. This means that,
// for now, we don't identify charged attacks reliably.
// - There might also be some cases where we incorrectly identify something as a normal or
// charged attack that is not (Diluc's E?).
// - Catalyst normal attacks have an ability, so we don't handle those for now.
// ToDo: Fix all of that.
if (ability != AbilityIdentifier.getDefaultInstance()) {
return;
}
// Handle the energy generation.
this.generateEnergyForNormalAndCharged(attackerEntity.get());
}
/*
* Energy logic related to using skills.
*/
private void handleBurstCast(Avatar avatar, int skillId) {
// Don't do anything if energy usage is disabled.
if (!GAME_OPTIONS.energyUsage || !this.energyUsage) {
return;
}
// If the cast skill was a burst, consume energy.
if (avatar.getSkillDepot() != null && skillId == avatar.getSkillDepot().getEnergySkill()) {
avatar.getAsEntity().clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START);
}
}
public void handleEvtDoSkillSuccNotify(GameSession session, int skillId, int casterId) {
// Determine the entity that has cast the skill. Cancel if we can't find that avatar.
Optional<EntityAvatar> caster =
this.player.getTeamManager().getActiveTeam().stream()
.filter(character -> character.getId() == casterId)
.findFirst();
if (caster.isEmpty()) {
return;
}
Avatar avatar = caster.get().getAvatar();
// Handle elemental burst.
this.handleBurstCast(avatar, skillId);
}
/*
* Monster energy drops.
*/
private void generateElemBallDrops(EntityMonster monster, int dropId) {
// Generate all drops specified for the given drop id.
if (!energyDropData.containsKey(dropId)) {
Grasscutter.getLogger().warn("No drop data for dropId {} found.", dropId);
return;
}
for (EnergyDropInfo info : energyDropData.get(dropId)) {
this.generateElemBall(info.getBallId(), monster.getPosition(), info.getCount());
}
}
public void handleMonsterEnergyDrop(
EntityMonster monster, float hpBeforeDamage, float hpAfterDamage) {
// Make sure this is actually a monster.
// Note that some wildlife also has that type, like boars or birds.
MonsterType type = monster.getMonsterData().getType();
if (type != MonsterType.MONSTER_ORDINARY && type != MonsterType.MONSTER_BOSS) {
return;
}
// Calculate the HP thresholds for before and after the damage was taken.
float maxHp = monster.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
float thresholdBefore = hpBeforeDamage / maxHp;
float thresholdAfter = hpAfterDamage / maxHp;
// Determine the thresholds the monster has passed, and generate drops based on that.
for (HpDrops drop : monster.getMonsterData().getHpDrops()) {
if (drop.getDropId() == 0) {
continue;
}
float threshold = drop.getHpPercent() / 100.0f;
if (threshold < thresholdBefore && threshold >= thresholdAfter) {
this.generateElemBallDrops(monster, drop.getDropId());
}
}
// Handle kill drops.
if (hpAfterDamage <= 0 && monster.getMonsterData().getKillDropId() != 0) {
this.generateElemBallDrops(monster, monster.getMonsterData().getKillDropId());
}
}
/*
* Utilities.
*/
private void generateElemBall(int ballId, Position position, int count) {
// Generate a particle/orb with the specified parameters.
ItemData itemData = GameData.getItemDataMap().get(ballId);
if (itemData == null) {
return;
}
EntityItem energyBall =
new EntityItem(this.getPlayer().getScene(), this.getPlayer(), itemData, position, count);
this.getPlayer().getScene().addEntity(energyBall);
}
private Optional<EntityAvatar> getCastingAvatarEntityForEnergy(int invokeEntityId) {
// To determine the avatar that has cast the skill that caused the energy particle to be
// generated,
// we have to look at the entity that has invoked the ability. This can either be that avatar
// directly,
// or it can be an `EntityClientGadget`, owned (some way up the owner hierarchy) by the avatar
// that cast the skill.
// Try to get the invoking entity from the scene.
GameEntity entity = this.player.getScene().getEntityById(invokeEntityId);
// Determine the ID of the entity that originally cast this skill. If the scene entity is null,
// or not an `EntityClientGadget`, we assume that we are directly looking at the casting avatar
// (the null case will happen if the avatar was switched out between casting the skill and the
// particle being generated). If the scene entity is an `EntityClientGadget`, we need to find
// the
// ID of the original owner of that gadget.
int avatarEntityId =
(!(entity instanceof EntityClientGadget))
? invokeEntityId
: ((EntityClientGadget) entity).getOriginalOwnerEntityId();
// Finally, find the avatar entity in the player's team.
return this.player.getTeamManager().getActiveTeam().stream()
.filter(character -> character.getId() == avatarEntityId)
.findFirst();
}
/**
* Refills the energy of the active avatar.
*
* @return True if the energy was refilled, false otherwise.
*/
public boolean refillActiveEnergy() {
var activeEntity = this.player.getTeamManager().getCurrentAvatarEntity();
return activeEntity.addEnergy(activeEntity.getAvatar().getSkillDepot().getEnergySkillData().getCostElemVal());
}
/**
* Refills the energy of the entire team.
*
* @param changeReason The reason for the energy change.
* @param isFlat Whether the energy should be added as a flat value.
*/
public void refillTeamEnergy(PropChangeReason changeReason, boolean isFlat) {
for (var entityAvatar : this.player.getTeamManager().getActiveTeam()) {
// giving the exact amount read off the AvatarSkillData.json
entityAvatar.addEnergy(entityAvatar.getAvatar().getSkillDepot()
.getEnergySkillData().getCostElemVal(), changeReason, isFlat);
}
}
public void setEnergyUsage(boolean energyUsage) {
this.energyUsage = energyUsage;
if (!energyUsage) { // Refill team energy if usage is disabled
for (EntityAvatar entityAvatar : this.player.getTeamManager().getActiveTeam()) {
entityAvatar.addEnergy(1000, PropChangeReason.PROP_CHANGE_REASON_GM, true);
}
}
}
}
package emu.grasscutter.game.managers.energy;
import static emu.grasscutter.config.Configuration.GAME_OPTIONS;
import com.google.protobuf.InvalidProtocolBufferException;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.avatar.AvatarSkillDepotData;
import emu.grasscutter.data.excels.monster.MonsterData.HpDrops;
import emu.grasscutter.game.avatar.Avatar;
import emu.grasscutter.game.entity.*;
import emu.grasscutter.game.player.BasePlayerManager;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.props.ElementType;
import emu.grasscutter.game.props.FightProperty;
import emu.grasscutter.game.props.MonsterType;
import emu.grasscutter.game.props.WeaponType;
import emu.grasscutter.net.proto.AbilityActionGenerateElemBallOuterClass.AbilityActionGenerateElemBall;
import emu.grasscutter.net.proto.AbilityIdentifierOuterClass.AbilityIdentifier;
import emu.grasscutter.net.proto.AbilityInvokeEntryOuterClass.AbilityInvokeEntry;
import emu.grasscutter.net.proto.AttackResultOuterClass.AttackResult;
import emu.grasscutter.net.proto.ChangeEnergyReasonOuterClass.ChangeEnergyReason;
import emu.grasscutter.net.proto.EvtBeingHitInfoOuterClass.EvtBeingHitInfo;
import emu.grasscutter.net.proto.PropChangeReasonOuterClass.PropChangeReason;
import emu.grasscutter.server.game.GameSession;
import emu.grasscutter.utils.Position;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import lombok.Getter;
public class EnergyManager extends BasePlayerManager {
private static final Int2ObjectMap<List<EnergyDropInfo>> energyDropData =
new Int2ObjectOpenHashMap<>();
private static final Int2ObjectMap<List<SkillParticleGenerationInfo>>
skillParticleGenerationData = new Int2ObjectOpenHashMap<>();
private final Object2IntMap<EntityAvatar> avatarNormalProbabilities;
@Getter private boolean energyUsage; // Should energy usage be enabled for this player?
public EnergyManager(Player player) {
super(player);
this.avatarNormalProbabilities = new Object2IntOpenHashMap<>();
this.energyUsage = GAME_OPTIONS.energyUsage;
}
public static void initialize() {
// Read the data we need for monster energy drops.
try {
DataLoader.loadList("EnergyDrop.json", EnergyDropEntry.class)
.forEach(
entry -> {
energyDropData.put(entry.getDropId(), entry.getDropList());
});
Grasscutter.getLogger().debug("Energy drop data successfully loaded.");
} catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load energy drop data.", ex);
}
// Read the data for particle generation from skills
try {
DataLoader.loadList("SkillParticleGeneration.json", SkillParticleGenerationEntry.class)
.forEach(
entry -> {
skillParticleGenerationData.put(entry.getAvatarId(), entry.getAmountList());
});
Grasscutter.getLogger().debug("Skill particle generation data successfully loaded.");
} catch (Exception ex) {
Grasscutter.getLogger().error("Unable to load skill particle generation data data.", ex);
}
}
/** Particle creation for elemental skills. */
private int getBallCountForAvatar(int avatarId) {
// We default to two particles.
int count = 2;
// If we don't have any data for this avatar, stop.
if (!skillParticleGenerationData.containsKey(avatarId)) {
Grasscutter.getLogger().warn("No particle generation data for avatarId {} found.", avatarId);
}
// If we do have data, roll for how many particles we should generate.
else {
int roll = ThreadLocalRandom.current().nextInt(0, 100);
int percentageStack = 0;
for (SkillParticleGenerationInfo info : skillParticleGenerationData.get(avatarId)) {
int chance = info.getChance();
percentageStack += chance;
if (roll < percentageStack) {
count = info.getValue();
break;
}
}
}
// Done.
return count;
}
private int getBallIdForElement(ElementType element) {
// If we have no element, we default to an element-less particle.
if (element == null) {
return 2024;
}
// Otherwise, we determine the particle's ID based on the element.
return switch (element) {
case Fire -> 2017;
case Water -> 2018;
case Grass -> 2019;
case Electric -> 2020;
case Wind -> 2021;
case Ice -> 2022;
case Rock -> 2023;
default -> 2024;
};
}
public void handleGenerateElemBall(AbilityInvokeEntry invoke)
throws InvalidProtocolBufferException {
// ToDo:
// This is also called when a weapon like Favonius Warbow etc. creates energy through its
// passive.
// We are not handling this correctly at the moment.
// Get action info.
AbilityActionGenerateElemBall action =
AbilityActionGenerateElemBall.parseFrom(invoke.getAbilityData());
if (action == null) {
return;
}
// Default to an elementless particle.
int itemId = 2024;
// Generate 2 particles by default.
int amount = 2;
// Try to get the casting avatar from the player's party.
Optional<EntityAvatar> avatarEntity =
this.getCastingAvatarEntityForEnergy(invoke.getEntityId());
// Bug: invokes twice sometimes, Ayato, Keqing
// ToDo: deal with press, hold difference. deal with charge(Beidou, Yunjin)
if (avatarEntity.isPresent()) {
Avatar avatar = avatarEntity.get().getAvatar();
if (avatar != null) {
int avatarId = avatar.getAvatarId();
AvatarSkillDepotData skillDepotData = avatar.getSkillDepot();
// Determine how many particles we need to create for this avatar.
amount = this.getBallCountForAvatar(avatarId);
// Determine the avatar's element, and based on that the ID of the
// particles we have to generate.
if (skillDepotData != null) {
ElementType element = skillDepotData.getElementType();
itemId = this.getBallIdForElement(element);
}
}
}
// Generate the particles.
var pos = new Position(action.getPos());
for (int i = 0; i < amount; i++) {
this.generateElemBall(itemId, pos, 1);
}
}
/**
* Energy generation for NAs/CAs.
*
* @param avatar The avatar.
*/
private void generateEnergyForNormalAndCharged(EntityAvatar avatar) {
// This logic is based on the descriptions given in
// https://genshin-impact.fandom.com/wiki/Energy#Energy_Generated_by_Normal_Attacks
// https://library.keqingmains.com/combat-mechanics/energy#auto-attacking
// Those descriptions are lacking in some information, so this implementation most likely
// does not fully replicate the behavior of the official server. Open questions:
// - Does the probability for a character reset after some time?
// - Does the probability for a character reset when switching them out?
// - Does this really count every individual hit separately?
// Get the avatar's weapon type.
WeaponType weaponType = avatar.getAvatar().getAvatarData().getWeaponType();
// Check if we already have probability data for this avatar. If not, insert it.
if (!this.avatarNormalProbabilities.containsKey(avatar)) {
this.avatarNormalProbabilities.put(avatar, weaponType.getEnergyGainInitialProbability());
}
// Roll for energy.
int currentProbability = this.avatarNormalProbabilities.getInt(avatar);
int roll = ThreadLocalRandom.current().nextInt(0, 100);
// If the player wins the roll, we increase the avatar's energy and reset the probability.
if (roll < currentProbability) {
avatar.addEnergy(1.0f, PropChangeReason.PROP_CHANGE_REASON_ABILITY, true);
this.avatarNormalProbabilities.put(avatar, weaponType.getEnergyGainInitialProbability());
}
// Otherwise, we increase the probability for the next hit.
else {
this.avatarNormalProbabilities.put(
avatar, currentProbability + weaponType.getEnergyGainIncreaseProbability());
}
}
public void handleAttackHit(EvtBeingHitInfo hitInfo) {
// Get the attack result.
AttackResult attackRes = hitInfo.getAttackResult();
// Make sure the attack was performed by the currently active avatar. If not, we ignore the hit.
Optional<EntityAvatar> attackerEntity =
this.getCastingAvatarEntityForEnergy(attackRes.getAttackerId());
if (attackerEntity.isEmpty()
|| this.player.getTeamManager().getCurrentAvatarEntity().getId()
!= attackerEntity.get().getId()) {
return;
}
// Make sure the target is an actual enemy.
GameEntity targetEntity = this.player.getScene().getEntityById(attackRes.getDefenseId());
if (!(targetEntity instanceof EntityMonster targetMonster)) {
return;
}
MonsterType targetType = targetMonster.getMonsterData().getType();
if (targetType != MonsterType.MONSTER_ORDINARY && targetType != MonsterType.MONSTER_BOSS) {
return;
}
// Get the ability that caused this hit.
AbilityIdentifier ability = attackRes.getAbilityIdentifier();
// Make sure there is no actual "ability" associated with the hit. For now, this is how we
// identify normal and charged attacks. Note that this is not completely accurate:
// - Many character's charged attacks have an ability associated with them. This means that,
// for now, we don't identify charged attacks reliably.
// - There might also be some cases where we incorrectly identify something as a normal or
// charged attack that is not (Diluc's E?).
// - Catalyst normal attacks have an ability, so we don't handle those for now.
// ToDo: Fix all of that.
if (ability != AbilityIdentifier.getDefaultInstance()) {
return;
}
// Handle the energy generation.
this.generateEnergyForNormalAndCharged(attackerEntity.get());
}
/*
* Energy logic related to using skills.
*/
private void handleBurstCast(Avatar avatar, int skillId) {
// Don't do anything if energy usage is disabled.
if (!GAME_OPTIONS.energyUsage || !this.energyUsage) {
return;
}
// If the cast skill was a burst, consume energy.
if (avatar.getSkillDepot() != null && skillId == avatar.getSkillDepot().getEnergySkill()) {
avatar.getAsEntity().clearEnergy(ChangeEnergyReason.CHANGE_ENERGY_REASON_SKILL_START);
}
}
public void handleEvtDoSkillSuccNotify(GameSession session, int skillId, int casterId) {
// Determine the entity that has cast the skill. Cancel if we can't find that avatar.
Optional<EntityAvatar> caster =
this.player.getTeamManager().getActiveTeam().stream()
.filter(character -> character.getId() == casterId)
.findFirst();
if (caster.isEmpty()) {
return;
}
Avatar avatar = caster.get().getAvatar();
// Handle elemental burst.
this.handleBurstCast(avatar, skillId);
}
/*
* Monster energy drops.
*/
private void generateElemBallDrops(EntityMonster monster, int dropId) {
// Generate all drops specified for the given drop id.
if (!energyDropData.containsKey(dropId)) {
Grasscutter.getLogger().warn("No drop data for dropId {} found.", dropId);
return;
}
for (EnergyDropInfo info : energyDropData.get(dropId)) {
this.generateElemBall(info.getBallId(), monster.getPosition(), info.getCount());
}
}
public void handleMonsterEnergyDrop(
EntityMonster monster, float hpBeforeDamage, float hpAfterDamage) {
// Make sure this is actually a monster.
// Note that some wildlife also has that type, like boars or birds.
MonsterType type = monster.getMonsterData().getType();
if (type != MonsterType.MONSTER_ORDINARY && type != MonsterType.MONSTER_BOSS) {
return;
}
// Calculate the HP thresholds for before and after the damage was taken.
float maxHp = monster.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
float thresholdBefore = hpBeforeDamage / maxHp;
float thresholdAfter = hpAfterDamage / maxHp;
// Determine the thresholds the monster has passed, and generate drops based on that.
for (HpDrops drop : monster.getMonsterData().getHpDrops()) {
if (drop.getDropId() == 0) {
continue;
}
float threshold = drop.getHpPercent() / 100.0f;
if (threshold < thresholdBefore && threshold >= thresholdAfter) {
this.generateElemBallDrops(monster, drop.getDropId());
}
}
// Handle kill drops.
if (hpAfterDamage <= 0 && monster.getMonsterData().getKillDropId() != 0) {
this.generateElemBallDrops(monster, monster.getMonsterData().getKillDropId());
}
}
/*
* Utilities.
*/
private void generateElemBall(int ballId, Position position, int count) {
// Generate a particle/orb with the specified parameters.
ItemData itemData = GameData.getItemDataMap().get(ballId);
if (itemData == null) {
return;
}
EntityItem energyBall =
new EntityItem(this.getPlayer().getScene(), this.getPlayer(), itemData, position, count);
this.getPlayer().getScene().addEntity(energyBall);
}
private Optional<EntityAvatar> getCastingAvatarEntityForEnergy(int invokeEntityId) {
// To determine the avatar that has cast the skill that caused the energy particle to be
// generated,
// we have to look at the entity that has invoked the ability. This can either be that avatar
// directly,
// or it can be an `EntityClientGadget`, owned (some way up the owner hierarchy) by the avatar
// that cast the skill.
// Try to get the invoking entity from the scene.
GameEntity entity = this.player.getScene().getEntityById(invokeEntityId);
// Determine the ID of the entity that originally cast this skill. If the scene entity is null,
// or not an `EntityClientGadget`, we assume that we are directly looking at the casting avatar
// (the null case will happen if the avatar was switched out between casting the skill and the
// particle being generated). If the scene entity is an `EntityClientGadget`, we need to find
// the
// ID of the original owner of that gadget.
int avatarEntityId =
(!(entity instanceof EntityClientGadget))
? invokeEntityId
: ((EntityClientGadget) entity).getOriginalOwnerEntityId();
// Finally, find the avatar entity in the player's team.
return this.player.getTeamManager().getActiveTeam().stream()
.filter(character -> character.getId() == avatarEntityId)
.findFirst();
}
/**
* Refills the energy of the active avatar.
*
* @return True if the energy was refilled, false otherwise.
*/
public boolean refillActiveEnergy() {
var activeEntity = this.player.getTeamManager().getCurrentAvatarEntity();
return activeEntity.addEnergy(
activeEntity.getAvatar().getSkillDepot().getEnergySkillData().getCostElemVal());
}
/**
* Refills the energy of the entire team.
*
* @param changeReason The reason for the energy change.
* @param isFlat Whether the energy should be added as a flat value.
*/
public void refillTeamEnergy(PropChangeReason changeReason, boolean isFlat) {
for (var entityAvatar : this.player.getTeamManager().getActiveTeam()) {
// giving the exact amount read off the AvatarSkillData.json
entityAvatar.addEnergy(
entityAvatar.getAvatar().getSkillDepot().getEnergySkillData().getCostElemVal(),
changeReason,
isFlat);
}
}
public void setEnergyUsage(boolean energyUsage) {
this.energyUsage = energyUsage;
if (!energyUsage) { // Refill team energy if usage is disabled
for (EntityAvatar entityAvatar : this.player.getTeamManager().getActiveTeam()) {
entityAvatar.addEnergy(1000, PropChangeReason.PROP_CHANGE_REASON_GM, true);
}
}
}
}

View File

@@ -1,280 +1,276 @@
package emu.grasscutter.game.player;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ScenePointEntry;
import emu.grasscutter.data.excels.OpenStateData;
import emu.grasscutter.data.excels.OpenStateData.OpenStateCondType;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.quest.enums.QuestCond;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.packet.send.*;
import lombok.val;
import java.util.Set;
import java.util.stream.Collectors;
// @Entity
public final class PlayerProgressManager extends BasePlayerDataManager {
/******************************************************************************************************************
******************************************************************************************************************
* OPEN STATES
******************************************************************************************************************
*****************************************************************************************************************/
// Set of open states that are never unlocked, whether they fulfill the conditions or not.
public static final Set<Integer> BLACKLIST_OPEN_STATES =
Set.of(
48 // blacklist OPEN_STATE_LIMIT_REGION_GLOBAL to make Meledy happy. =D Remove this as
// soon as quest unlocks are fully implemented.
);
// Set of open states that are set per default for all accounts. Can be overwritten by an entry in
// `map`.
public static final Set<Integer> DEFAULT_OPEN_STATES =
GameData.getOpenStateList().stream()
.filter(
s ->
s.isDefaultState() // Actual default-opened states.
// All states whose unlock we don't handle correctly yet.
|| (s.getCond().stream()
.filter(
c ->
c.getCondType()
== OpenStateCondType.OPEN_STATE_COND_PLAYER_LEVEL)
.count()
== 0)
// Always unlock OPEN_STATE_PAIMON, otherwise the player will not have a
// working chat.
|| s.getId() == 1)
.filter(
s ->
!BLACKLIST_OPEN_STATES.contains(s.getId())) // Filter out states in the blacklist.
.map(s -> s.getId())
.collect(Collectors.toSet());
public PlayerProgressManager(Player player) {
super(player);
}
/**********
* Handler for player login.
**********/
public void onPlayerLogin() {
// Try unlocking open states on player login. This handles accounts where unlock conditions were
// already met before certain open state unlocks were implemented.
this.tryUnlockOpenStates(false);
// Send notify to the client.
player.getSession().send(new PacketOpenStateUpdateNotify(this.player));
// Add statue quests if necessary.
this.addStatueQuestsOnLogin();
// Auto-unlock the first statue and map area, until we figure out how to make
// that particular statue interactable.
this.player.getUnlockedScenePoints(3).add(7);
this.player.getUnlockedSceneAreas(3).add(1);
}
/**********
* Direct getters and setters for open states.
**********/
public int getOpenState(int openState) {
return this.player.getOpenStates().getOrDefault(openState, 0);
}
private void setOpenState(int openState, int value, boolean sendNotify) {
int previousValue = this.player.getOpenStates().getOrDefault(openState, 0);
if (value != previousValue) {
this.player.getOpenStates().put(openState, value);
if (sendNotify) {
player.getSession().send(new PacketOpenStateChangeNotify(openState, value));
}
}
}
private void setOpenState(int openState, int value) {
this.setOpenState(openState, value, true);
}
/**********
* Condition checking for setting open states.
**********/
private boolean areConditionsMet(OpenStateData openState) {
// Check all conditions and test if at least one of them is violated.
for (var condition : openState.getCond()) {
// For level conditions, check if the player has reached the necessary level.
if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_PLAYER_LEVEL) {
if (this.player.getLevel() < condition.getParam()) {
return false;
}
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_QUEST) {
// ToDo: Implement.
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_PARENT_QUEST) {
// ToDo: Implement.
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_OFFERING_LEVEL) {
// ToDo: Implement.
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_CITY_REPUTATION_LEVEL) {
// ToDo: Implement.
}
}
// Done. If we didn't find any violations, all conditions are met.
return true;
}
/**********
* Setting open states from the client (via `SetOpenStateReq`).
**********/
public void setOpenStateFromClient(int openState, int value) {
// Get the data for this open state.
OpenStateData data = GameData.getOpenStateDataMap().get(openState);
if (data == null) {
this.player.sendPacket(new PacketSetOpenStateRsp(Retcode.RET_FAIL));
return;
}
// Make sure that this is an open state that the client is allowed to set,
// and that it doesn't have any further conditions attached.
if (!data.isAllowClientOpen() || !this.areConditionsMet(data)) {
this.player.sendPacket(new PacketSetOpenStateRsp(Retcode.RET_FAIL));
return;
}
// Set.
this.setOpenState(openState, value);
this.player.sendPacket(new PacketSetOpenStateRsp(openState, value));
}
/**
* This force sets an open state, ignoring all conditions and permissions
*/
public void forceSetOpenState(int openState, int value) {
this.setOpenState(openState, value);
}
/**********
* Triggered unlocking of open states (unlock states whose conditions have been met.)
**********/
public void tryUnlockOpenStates(boolean sendNotify) {
// Get list of open states that are not yet unlocked.
var lockedStates =
GameData.getOpenStateList().stream()
.filter(s -> this.player.getOpenStates().getOrDefault(s, 0) == 0)
.toList();
// Try unlocking all of them.
for (var state : lockedStates) {
// To auto-unlock a state, it has to meet three conditions:
// * it can not be a state that is unlocked by the client,
// * it has to meet all its unlock conditions, and
// * it can not be in the blacklist.
if (!state.isAllowClientOpen()
&& this.areConditionsMet(state)
&& !BLACKLIST_OPEN_STATES.contains(state.getId())) {
this.setOpenState(state.getId(), 1, sendNotify);
}
}
}
public void tryUnlockOpenStates() {
this.tryUnlockOpenStates(true);
}
/******************************************************************************************************************
******************************************************************************************************************
* MAP AREAS AND POINTS
******************************************************************************************************************
*****************************************************************************************************************/
private void addStatueQuestsOnLogin() {
// Get all currently existing subquests for the "unlock all statues" main quest.
var statueMainQuest = GameData.getMainQuestDataMap().get(303);
var statueSubQuests = statueMainQuest.getSubQuests();
// Add the main statue quest if it isn't active yet.
var statueGameMainQuest = this.player.getQuestManager().getMainQuestById(303);
if (statueGameMainQuest == null) {
this.player.getQuestManager().addQuest(30302);
statueGameMainQuest = this.player.getQuestManager().getMainQuestById(303);
}
// Set all subquests to active if they aren't already finished.
for (var subData : statueSubQuests) {
var subGameQuest = statueGameMainQuest.getChildQuestById(subData.getSubId());
if (subGameQuest != null && subGameQuest.getState() == QuestState.QUEST_STATE_UNSTARTED) {
this.player.getQuestManager().addQuest(subData.getSubId());
}
}
}
public boolean unlockTransPoint(int sceneId, int pointId, boolean isStatue) {
// Check whether the unlocked point exists and whether it is still locked.
ScenePointEntry scenePointEntry = GameData.getScenePointEntryById(sceneId, pointId);
if (scenePointEntry == null || this.player.getUnlockedScenePoints(sceneId).contains(pointId)) {
return false;
}
// Add the point to the list of unlocked points for its scene.
this.player.getUnlockedScenePoints(sceneId).add(pointId);
// Give primogems and Adventure EXP for unlocking.
this.player.getInventory().addItem(201, 5, ActionReason.UnlockPointReward);
this.player.getInventory().addItem(102, isStatue ? 50 : 10, ActionReason.UnlockPointReward);
// this.player.sendPacket(new
// PacketPlayerPropChangeReasonNotify(this.player.getProperty(PlayerProperty.PROP_PLAYER_EXP),
// PlayerProperty.PROP_PLAYER_EXP, PropChangeReason.PROP_CHANGE_REASON_PLAYER_ADD_EXP));
// Fire quest trigger for trans point unlock.
this.player
.getQuestManager()
.queueEvent(QuestContent.QUEST_CONTENT_UNLOCK_TRANS_POINT, sceneId, pointId);
// Send packet.
this.player.sendPacket(new PacketScenePointUnlockNotify(sceneId, pointId));
return true;
}
public void unlockSceneArea(int sceneId, int areaId) {
// Add the area to the list of unlocked areas in its scene.
this.player.getUnlockedSceneAreas(sceneId).add(areaId);
// Send packet.
this.player.sendPacket(new PacketSceneAreaUnlockNotify(sceneId, areaId));
}
/**
* Give replace costume to player (Amber, Jean, Mona, Rosaria)
*/
public void addReplaceCostumes(){
var currentPlayerCostumes = player.getCostumeList();
GameData.getAvatarReplaceCostumeDataMap().keySet().forEach(costumeId -> {
if (GameData.getAvatarCostumeDataMap().get(costumeId) == null || currentPlayerCostumes.contains(costumeId)){
return;
}
this.player.addCostume(costumeId);
});
}
/**
* Quest progress
*/
public void addQuestProgress(int id, int count){
var newCount = player.getPlayerProgress().addToCurrentProgress(id, count);
player.save();
player.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_ADD_QUEST_PROGRESS, id, newCount);
}
/**
* Item history
*/
public void addItemObtainedHistory(int id, int count){
var newCount = player.getPlayerProgress().addToItemHistory(id, count);
player.save();
player.getQuestManager().queueEvent(QuestCond.QUEST_COND_HISTORY_GOT_ANY_ITEM, id, newCount);
}
}
package emu.grasscutter.game.player;
import emu.grasscutter.data.GameData;
import emu.grasscutter.data.binout.ScenePointEntry;
import emu.grasscutter.data.excels.OpenStateData;
import emu.grasscutter.data.excels.OpenStateData.OpenStateCondType;
import emu.grasscutter.game.props.ActionReason;
import emu.grasscutter.game.quest.enums.QuestCond;
import emu.grasscutter.game.quest.enums.QuestContent;
import emu.grasscutter.game.quest.enums.QuestState;
import emu.grasscutter.net.proto.RetcodeOuterClass.Retcode;
import emu.grasscutter.server.packet.send.*;
import java.util.Set;
import java.util.stream.Collectors;
// @Entity
public final class PlayerProgressManager extends BasePlayerDataManager {
/******************************************************************************************************************
******************************************************************************************************************
* OPEN STATES
******************************************************************************************************************
*****************************************************************************************************************/
// Set of open states that are never unlocked, whether they fulfill the conditions or not.
public static final Set<Integer> BLACKLIST_OPEN_STATES =
Set.of(
48 // blacklist OPEN_STATE_LIMIT_REGION_GLOBAL to make Meledy happy. =D Remove this as
// soon as quest unlocks are fully implemented.
);
// Set of open states that are set per default for all accounts. Can be overwritten by an entry in
// `map`.
public static final Set<Integer> DEFAULT_OPEN_STATES =
GameData.getOpenStateList().stream()
.filter(
s ->
s.isDefaultState() // Actual default-opened states.
// All states whose unlock we don't handle correctly yet.
|| (s.getCond().stream()
.filter(
c ->
c.getCondType()
== OpenStateCondType.OPEN_STATE_COND_PLAYER_LEVEL)
.count()
== 0)
// Always unlock OPEN_STATE_PAIMON, otherwise the player will not have a
// working chat.
|| s.getId() == 1)
.filter(
s ->
!BLACKLIST_OPEN_STATES.contains(s.getId())) // Filter out states in the blacklist.
.map(s -> s.getId())
.collect(Collectors.toSet());
public PlayerProgressManager(Player player) {
super(player);
}
/**********
* Handler for player login.
**********/
public void onPlayerLogin() {
// Try unlocking open states on player login. This handles accounts where unlock conditions were
// already met before certain open state unlocks were implemented.
this.tryUnlockOpenStates(false);
// Send notify to the client.
player.getSession().send(new PacketOpenStateUpdateNotify(this.player));
// Add statue quests if necessary.
this.addStatueQuestsOnLogin();
// Auto-unlock the first statue and map area, until we figure out how to make
// that particular statue interactable.
this.player.getUnlockedScenePoints(3).add(7);
this.player.getUnlockedSceneAreas(3).add(1);
}
/**********
* Direct getters and setters for open states.
**********/
public int getOpenState(int openState) {
return this.player.getOpenStates().getOrDefault(openState, 0);
}
private void setOpenState(int openState, int value, boolean sendNotify) {
int previousValue = this.player.getOpenStates().getOrDefault(openState, 0);
if (value != previousValue) {
this.player.getOpenStates().put(openState, value);
if (sendNotify) {
player.getSession().send(new PacketOpenStateChangeNotify(openState, value));
}
}
}
private void setOpenState(int openState, int value) {
this.setOpenState(openState, value, true);
}
/**********
* Condition checking for setting open states.
**********/
private boolean areConditionsMet(OpenStateData openState) {
// Check all conditions and test if at least one of them is violated.
for (var condition : openState.getCond()) {
// For level conditions, check if the player has reached the necessary level.
if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_PLAYER_LEVEL) {
if (this.player.getLevel() < condition.getParam()) {
return false;
}
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_QUEST) {
// ToDo: Implement.
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_COND_PARENT_QUEST) {
// ToDo: Implement.
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_OFFERING_LEVEL) {
// ToDo: Implement.
} else if (condition.getCondType() == OpenStateCondType.OPEN_STATE_CITY_REPUTATION_LEVEL) {
// ToDo: Implement.
}
}
// Done. If we didn't find any violations, all conditions are met.
return true;
}
/**********
* Setting open states from the client (via `SetOpenStateReq`).
**********/
public void setOpenStateFromClient(int openState, int value) {
// Get the data for this open state.
OpenStateData data = GameData.getOpenStateDataMap().get(openState);
if (data == null) {
this.player.sendPacket(new PacketSetOpenStateRsp(Retcode.RET_FAIL));
return;
}
// Make sure that this is an open state that the client is allowed to set,
// and that it doesn't have any further conditions attached.
if (!data.isAllowClientOpen() || !this.areConditionsMet(data)) {
this.player.sendPacket(new PacketSetOpenStateRsp(Retcode.RET_FAIL));
return;
}
// Set.
this.setOpenState(openState, value);
this.player.sendPacket(new PacketSetOpenStateRsp(openState, value));
}
/** This force sets an open state, ignoring all conditions and permissions */
public void forceSetOpenState(int openState, int value) {
this.setOpenState(openState, value);
}
/**********
* Triggered unlocking of open states (unlock states whose conditions have been met.)
**********/
public void tryUnlockOpenStates(boolean sendNotify) {
// Get list of open states that are not yet unlocked.
var lockedStates =
GameData.getOpenStateList().stream()
.filter(s -> this.player.getOpenStates().getOrDefault(s, 0) == 0)
.toList();
// Try unlocking all of them.
for (var state : lockedStates) {
// To auto-unlock a state, it has to meet three conditions:
// * it can not be a state that is unlocked by the client,
// * it has to meet all its unlock conditions, and
// * it can not be in the blacklist.
if (!state.isAllowClientOpen()
&& this.areConditionsMet(state)
&& !BLACKLIST_OPEN_STATES.contains(state.getId())) {
this.setOpenState(state.getId(), 1, sendNotify);
}
}
}
public void tryUnlockOpenStates() {
this.tryUnlockOpenStates(true);
}
/******************************************************************************************************************
******************************************************************************************************************
* MAP AREAS AND POINTS
******************************************************************************************************************
*****************************************************************************************************************/
private void addStatueQuestsOnLogin() {
// Get all currently existing subquests for the "unlock all statues" main quest.
var statueMainQuest = GameData.getMainQuestDataMap().get(303);
var statueSubQuests = statueMainQuest.getSubQuests();
// Add the main statue quest if it isn't active yet.
var statueGameMainQuest = this.player.getQuestManager().getMainQuestById(303);
if (statueGameMainQuest == null) {
this.player.getQuestManager().addQuest(30302);
statueGameMainQuest = this.player.getQuestManager().getMainQuestById(303);
}
// Set all subquests to active if they aren't already finished.
for (var subData : statueSubQuests) {
var subGameQuest = statueGameMainQuest.getChildQuestById(subData.getSubId());
if (subGameQuest != null && subGameQuest.getState() == QuestState.QUEST_STATE_UNSTARTED) {
this.player.getQuestManager().addQuest(subData.getSubId());
}
}
}
public boolean unlockTransPoint(int sceneId, int pointId, boolean isStatue) {
// Check whether the unlocked point exists and whether it is still locked.
ScenePointEntry scenePointEntry = GameData.getScenePointEntryById(sceneId, pointId);
if (scenePointEntry == null || this.player.getUnlockedScenePoints(sceneId).contains(pointId)) {
return false;
}
// Add the point to the list of unlocked points for its scene.
this.player.getUnlockedScenePoints(sceneId).add(pointId);
// Give primogems and Adventure EXP for unlocking.
this.player.getInventory().addItem(201, 5, ActionReason.UnlockPointReward);
this.player.getInventory().addItem(102, isStatue ? 50 : 10, ActionReason.UnlockPointReward);
// this.player.sendPacket(new
// PacketPlayerPropChangeReasonNotify(this.player.getProperty(PlayerProperty.PROP_PLAYER_EXP),
// PlayerProperty.PROP_PLAYER_EXP, PropChangeReason.PROP_CHANGE_REASON_PLAYER_ADD_EXP));
// Fire quest trigger for trans point unlock.
this.player
.getQuestManager()
.queueEvent(QuestContent.QUEST_CONTENT_UNLOCK_TRANS_POINT, sceneId, pointId);
// Send packet.
this.player.sendPacket(new PacketScenePointUnlockNotify(sceneId, pointId));
return true;
}
public void unlockSceneArea(int sceneId, int areaId) {
// Add the area to the list of unlocked areas in its scene.
this.player.getUnlockedSceneAreas(sceneId).add(areaId);
// Send packet.
this.player.sendPacket(new PacketSceneAreaUnlockNotify(sceneId, areaId));
}
/** Give replace costume to player (Amber, Jean, Mona, Rosaria) */
public void addReplaceCostumes() {
var currentPlayerCostumes = player.getCostumeList();
GameData.getAvatarReplaceCostumeDataMap()
.keySet()
.forEach(
costumeId -> {
if (GameData.getAvatarCostumeDataMap().get(costumeId) == null
|| currentPlayerCostumes.contains(costumeId)) {
return;
}
this.player.addCostume(costumeId);
});
}
/** Quest progress */
public void addQuestProgress(int id, int count) {
var newCount = player.getPlayerProgress().addToCurrentProgress(id, count);
player.save();
player
.getQuestManager()
.queueEvent(QuestContent.QUEST_CONTENT_ADD_QUEST_PROGRESS, id, newCount);
}
/** Item history */
public void addItemObtainedHistory(int id, int count) {
var newCount = player.getPlayerProgress().addToItemHistory(id, count);
player.save();
player.getQuestManager().queueEvent(QuestCond.QUEST_COND_HISTORY_GOT_ANY_ITEM, id, newCount);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,72 +1,131 @@
package emu.grasscutter.game.props;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
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 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,23 +1,21 @@
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond;
import emu.grasscutter.game.quest.enums.QuestCond;
import lombok.val;
@QuestValueCond(QuestCond.QUEST_COND_PERSONAL_LINE_UNLOCK)
public class ConditionPersonalLineUnlock extends BaseCondition {
@Override
public boolean execute(
Player owner,
QuestData questData,
QuestData.QuestAcceptCondition condition,
String paramStr,
int... params
) {
var personalLineId = condition.getParam()[0];
return owner.getPersonalLineList().contains(personalLineId);
}
}
package emu.grasscutter.game.quest.conditions;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.game.quest.QuestValueCond;
import emu.grasscutter.game.quest.enums.QuestCond;
@QuestValueCond(QuestCond.QUEST_COND_PERSONAL_LINE_UNLOCK)
public class ConditionPersonalLineUnlock extends BaseCondition {
@Override
public boolean execute(
Player owner,
QuestData questData,
QuestData.QuestAcceptCondition condition,
String paramStr,
int... params) {
var personalLineId = condition.getParam()[0];
return owner.getPersonalLineList().contains(personalLineId);
}
}

View File

@@ -1,17 +1,17 @@
package emu.grasscutter.game.quest.exec;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueExec;
import emu.grasscutter.game.quest.enums.QuestExec;
import emu.grasscutter.game.quest.handlers.QuestExecHandler;
@QuestValueExec(QuestExec.QUEST_EXEC_ADD_CUR_AVATAR_ENERGY)
public class ExecAddCurAvatarEnergy extends QuestExecHandler {
@Override
public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) {
Grasscutter.getLogger().info("Energy refilled");
return quest.getOwner().getEnergyManager().refillActiveEnergy();
}
}
package emu.grasscutter.game.quest.exec;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueExec;
import emu.grasscutter.game.quest.enums.QuestExec;
import emu.grasscutter.game.quest.handlers.QuestExecHandler;
@QuestValueExec(QuestExec.QUEST_EXEC_ADD_CUR_AVATAR_ENERGY)
public class ExecAddCurAvatarEnergy extends QuestExecHandler {
@Override
public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) {
Grasscutter.getLogger().info("Energy refilled");
return quest.getOwner().getEnergyManager().refillActiveEnergy();
}
}

View File

@@ -1,21 +1,21 @@
package emu.grasscutter.game.quest.exec;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueExec;
import emu.grasscutter.game.quest.enums.QuestExec;
import emu.grasscutter.game.quest.handlers.QuestExecHandler;
import java.util.Arrays;
@QuestValueExec(QuestExec.QUEST_EXEC_ADD_QUEST_PROGRESS)
public final class ExecAddQuestProgress extends QuestExecHandler {
@Override
public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) {
var param =
Arrays.stream(paramStr).filter(i -> !i.isBlank()).mapToInt(Integer::parseInt).toArray();
quest.getOwner().getProgressManager().addQuestProgress(param[0], param[1]);
return true;
}
}
package emu.grasscutter.game.quest.exec;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueExec;
import emu.grasscutter.game.quest.enums.QuestExec;
import emu.grasscutter.game.quest.handlers.QuestExecHandler;
import java.util.Arrays;
@QuestValueExec(QuestExec.QUEST_EXEC_ADD_QUEST_PROGRESS)
public final class ExecAddQuestProgress extends QuestExecHandler {
@Override
public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) {
var param =
Arrays.stream(paramStr).filter(i -> !i.isBlank()).mapToInt(Integer::parseInt).toArray();
quest.getOwner().getProgressManager().addQuestProgress(param[0], param[1]);
return true;
}
}

View File

@@ -1,20 +1,20 @@
package emu.grasscutter.game.quest.exec;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueExec;
import emu.grasscutter.game.quest.enums.QuestExec;
import emu.grasscutter.game.quest.handlers.QuestExecHandler;
import java.util.Arrays;
@QuestValueExec(QuestExec.QUEST_EXEC_SET_OPEN_STATE)
public class ExecSetOpenState extends QuestExecHandler {
@Override
public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) {
var param =
Arrays.stream(paramStr).filter(i -> !i.isBlank()).mapToInt(Integer::parseInt).toArray();
quest.getOwner().getProgressManager().forceSetOpenState(param[0], param[1]);
return true;
}
}
package emu.grasscutter.game.quest.exec;
import emu.grasscutter.data.excels.QuestData;
import emu.grasscutter.game.quest.GameQuest;
import emu.grasscutter.game.quest.QuestValueExec;
import emu.grasscutter.game.quest.enums.QuestExec;
import emu.grasscutter.game.quest.handlers.QuestExecHandler;
import java.util.Arrays;
@QuestValueExec(QuestExec.QUEST_EXEC_SET_OPEN_STATE)
public class ExecSetOpenState extends QuestExecHandler {
@Override
public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) {
var param =
Arrays.stream(paramStr).filter(i -> !i.isBlank()).mapToInt(Integer::parseInt).toArray();
quest.getOwner().getProgressManager().forceSetOpenState(param[0], param[1]);
return true;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,86 +1,85 @@
package emu.grasscutter.game.world;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.bson.types.ObjectId;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.SceneGroup;
import lombok.Getter;
import lombok.Setter;
@Entity(value = "group_instances", useDiscriminator = false)
public final class SceneGroupInstance {
@Id private ObjectId id;
@Indexed private int ownerUid; //This group is owned by the host player
@Getter private int groupId;
@Getter private transient SceneGroup luaGroup;
@Getter @Setter private int targetSuiteId;
@Getter @Setter private int activeSuiteId;
@Getter private Set<Integer> deadEntities; //Config_ids
private boolean isCached;
@Getter private Map<Integer, Integer> cachedGadgetStates;
@Getter private Map<String, Integer> cachedVariables;
@Getter @Setter private int lastTimeRefreshed;
public SceneGroupInstance(SceneGroup group, Player owner) {
this.luaGroup = group;
this.groupId = group.id;
this.targetSuiteId = 0;
this.activeSuiteId = 0;
this.lastTimeRefreshed = 0;
this.ownerUid = owner.getUid();
this.deadEntities = new HashSet<>();
this.cachedGadgetStates = new ConcurrentHashMap<>();
this.cachedVariables = new ConcurrentHashMap<>();
this.isCached = false; //This is true when the group is not loaded on scene but caches suite data
}
@Deprecated // Morphia only!
SceneGroupInstance(){
this.cachedVariables = new ConcurrentHashMap<>();
this.deadEntities = new HashSet<>();
this.cachedGadgetStates = new ConcurrentHashMap<>();
}
public void setLuaGroup(SceneGroup group) {
this.luaGroup = group;
this.groupId = group.id;
}
public boolean isCached() {
return this.isCached;
}
public void setCached(boolean value) {
this.isCached = value;
save(); //Save each time a group is registered or unregistered
}
public void cacheGadgetState(SceneGadget g, int state) {
if(g.persistent) //Only cache when is persistent
cachedGadgetStates.put(g.config_id, state);
}
public int getCachedGadgetState(SceneGadget g) {
Integer state = cachedGadgetStates.getOrDefault(g.config_id, null);
return (state == null) ? g.state : state;
}
public void save() {
DatabaseHelper.saveGroupInstance(this);
}
}
package emu.grasscutter.game.world;
import dev.morphia.annotations.Entity;
import dev.morphia.annotations.Id;
import dev.morphia.annotations.Indexed;
import emu.grasscutter.database.DatabaseHelper;
import emu.grasscutter.game.player.Player;
import emu.grasscutter.scripts.data.SceneGadget;
import emu.grasscutter.scripts.data.SceneGroup;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import lombok.Getter;
import lombok.Setter;
import org.bson.types.ObjectId;
@Entity(value = "group_instances", useDiscriminator = false)
public final class SceneGroupInstance {
@Id private ObjectId id;
@Indexed private int ownerUid; // This group is owned by the host player
@Getter private int groupId;
@Getter private transient SceneGroup luaGroup;
@Getter @Setter private int targetSuiteId;
@Getter @Setter private int activeSuiteId;
@Getter private Set<Integer> deadEntities; // Config_ids
private boolean isCached;
@Getter private Map<Integer, Integer> cachedGadgetStates;
@Getter private Map<String, Integer> cachedVariables;
@Getter @Setter private int lastTimeRefreshed;
public SceneGroupInstance(SceneGroup group, Player owner) {
this.luaGroup = group;
this.groupId = group.id;
this.targetSuiteId = 0;
this.activeSuiteId = 0;
this.lastTimeRefreshed = 0;
this.ownerUid = owner.getUid();
this.deadEntities = new HashSet<>();
this.cachedGadgetStates = new ConcurrentHashMap<>();
this.cachedVariables = new ConcurrentHashMap<>();
this.isCached =
false; // This is true when the group is not loaded on scene but caches suite data
}
@Deprecated // Morphia only!
SceneGroupInstance() {
this.cachedVariables = new ConcurrentHashMap<>();
this.deadEntities = new HashSet<>();
this.cachedGadgetStates = new ConcurrentHashMap<>();
}
public void setLuaGroup(SceneGroup group) {
this.luaGroup = group;
this.groupId = group.id;
}
public boolean isCached() {
return this.isCached;
}
public void setCached(boolean value) {
this.isCached = value;
save(); // Save each time a group is registered or unregistered
}
public void cacheGadgetState(SceneGadget g, int state) {
if (g.persistent) // Only cache when is persistent
cachedGadgetStates.put(g.config_id, state);
}
public int getCachedGadgetState(SceneGadget g) {
Integer state = cachedGadgetStates.getOrDefault(g.config_id, null);
return (state == null) ? g.state : state;
}
public void save() {
DatabaseHelper.saveGroupInstance(this);
}
}