mirror of
https://github.com/Grasscutters/Grasscutter.git
synced 2025-12-20 02:45:52 +01:00
Continue updating/refactoring classes
Most code is matched from `Grasscutter-Quests`.
This commit is contained in:
@@ -1,127 +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;
|
||||
|
||||
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
@@ -1,14 +1,18 @@
|
||||
package emu.grasscutter.game.dungeons;
|
||||
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
|
||||
public class BasicDungeonSettleListener implements DungeonSettleListener {
|
||||
|
||||
@Override
|
||||
public void onDungeonSettle(Scene scene) {
|
||||
scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000);
|
||||
scene.broadcastPacket(new PacketDungeonSettleNotify(scene.getChallenge()));
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.game.dungeons;
|
||||
|
||||
import emu.grasscutter.game.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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,332 +1,314 @@
|
||||
package emu.grasscutter.game.dungeons;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.common.ItemParamData;
|
||||
import emu.grasscutter.data.excels.dungeon.DungeonData;
|
||||
import emu.grasscutter.data.excels.dungeon.DungeonPassConfigData;
|
||||
import emu.grasscutter.game.activity.trialavatar.TrialAvatarActivityHandler;
|
||||
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult;
|
||||
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
|
||||
import emu.grasscutter.game.inventory.GameItem;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.props.ActionReason;
|
||||
import emu.grasscutter.game.props.ActivityType;
|
||||
import emu.grasscutter.game.props.WatcherTriggerType;
|
||||
import emu.grasscutter.game.quest.enums.LogicType;
|
||||
import emu.grasscutter.game.quest.enums.QuestContent;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.scripts.constants.EventType;
|
||||
import emu.grasscutter.scripts.data.ScriptArgs;
|
||||
import emu.grasscutter.server.packet.send.PacketDungeonWayPointNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketGadgetAutoPickDropInfoNotify;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import javax.annotation.Nullable;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.val;
|
||||
|
||||
/**
|
||||
* TODO handle time limits TODO handle respawn points TODO handle team wipes and respawns TODO check
|
||||
* monster level and levelConfigMap
|
||||
*/
|
||||
public class DungeonManager {
|
||||
|
||||
@Getter private final Scene scene;
|
||||
@Getter private final DungeonData dungeonData;
|
||||
@Getter private final DungeonPassConfigData passConfigData;
|
||||
|
||||
@Getter private final int[] finishedConditions;
|
||||
private final IntSet rewardedPlayers = new IntOpenHashSet();
|
||||
private final Set<Integer> activeDungeonWayPoints = new HashSet<>();
|
||||
private boolean ended = false;
|
||||
private int newestWayPoint = 0;
|
||||
@Getter private int startSceneTime = 0;
|
||||
|
||||
DungeonTrialTeam trialTeam = null;
|
||||
|
||||
public DungeonManager(@NonNull Scene scene, @NonNull DungeonData dungeonData) {
|
||||
this.scene = scene;
|
||||
this.dungeonData = dungeonData;
|
||||
this.passConfigData = GameData.getDungeonPassConfigDataMap().get(dungeonData.getPassCond());
|
||||
this.finishedConditions = new int[passConfigData.getConds().size()];
|
||||
this.scene.setDungeonManager(this);
|
||||
}
|
||||
|
||||
public void triggerEvent(DungeonPassConditionType conditionType, int... params) {
|
||||
if (ended) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < passConfigData.getConds().size(); i++) {
|
||||
var cond = passConfigData.getConds().get(i);
|
||||
if (conditionType == cond.getCondType()) {
|
||||
if (getScene().getWorld().getServer().getDungeonSystem().triggerCondition(cond, params)) {
|
||||
finishedConditions[i] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isFinishedSuccessfully()) {
|
||||
finishDungeon();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFinishedSuccessfully() {
|
||||
return LogicType.calculate(passConfigData.getLogicType(), finishedConditions);
|
||||
}
|
||||
|
||||
public int getLevelForMonster(int id) {
|
||||
// TODO should use levelConfigMap? and how?
|
||||
return dungeonData.getShowLevel();
|
||||
}
|
||||
|
||||
public boolean activateRespawnPoint(int pointId) {
|
||||
val respawnPoint = GameData.getScenePointEntryById(scene.getId(), pointId);
|
||||
|
||||
if (respawnPoint == null) {
|
||||
Grasscutter.getLogger().warn("trying to activate unknown respawn point {}", pointId);
|
||||
return false;
|
||||
}
|
||||
|
||||
scene.broadcastPacket(
|
||||
new PacketDungeonWayPointNotify(
|
||||
activeDungeonWayPoints.add(pointId), activeDungeonWayPoints));
|
||||
newestWayPoint = pointId;
|
||||
|
||||
Grasscutter.getLogger().debug("[unimplemented respawn] activated respawn point {}", pointId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable public Position getRespawnLocation() {
|
||||
if (newestWayPoint == 0) { // validity is checked before setting it, so if != 0 its always valid
|
||||
return null;
|
||||
}
|
||||
val pointData = GameData.getScenePointEntryById(scene.getId(), newestWayPoint).getPointData();
|
||||
return pointData.getTranPos() != null ? pointData.getTranPos() : pointData.getPos();
|
||||
}
|
||||
|
||||
public Position getRespawnRotation() {
|
||||
if (newestWayPoint == 0) { // validity is checked before setting it, so if != 0 its always valid
|
||||
return null;
|
||||
}
|
||||
val pointData = GameData.getScenePointEntryById(scene.getId(), newestWayPoint).getPointData();
|
||||
return pointData.getRot() != null ? pointData.getRot() : null;
|
||||
}
|
||||
|
||||
public boolean getStatueDrops(Player player, boolean useCondensed, int groupId) {
|
||||
if (!isFinishedSuccessfully()
|
||||
|| dungeonData.getRewardPreviewData() == null
|
||||
|| dungeonData.getRewardPreviewData().getPreviewItems().length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Already rewarded
|
||||
if (rewardedPlayers.contains(player.getUid())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!handleCost(player, useCondensed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get and roll rewards.
|
||||
List<GameItem> rewards = new ArrayList<>(this.rollRewards(useCondensed));
|
||||
// Add rewards to player and send notification.
|
||||
player.getInventory().addItems(rewards, ActionReason.DungeonStatueDrop);
|
||||
player.sendPacket(new PacketGadgetAutoPickDropInfoNotify(rewards));
|
||||
|
||||
rewardedPlayers.add(player.getUid());
|
||||
|
||||
scene.getScriptManager().callEvent(new ScriptArgs(groupId, EventType.EVENT_DUNGEON_REWARD_GET));
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean handleCost(Player player, boolean useCondensed) {
|
||||
int resinCost = dungeonData.getStatueCostCount() != 0 ? dungeonData.getStatueCostCount() : 20;
|
||||
if (resinCost == 0) {
|
||||
return true;
|
||||
}
|
||||
if (useCondensed) {
|
||||
// Check if condensed resin is usable here.
|
||||
// For this, we use the following logic for now:
|
||||
// The normal resin cost of the dungeon has to be 20.
|
||||
if (resinCost != 20) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Spend the condensed resin and only proceed if the transaction succeeds.
|
||||
return player.getResinManager().useCondensedResin(1);
|
||||
} else if (dungeonData.getStatueCostID() == 106) {
|
||||
// Spend the resin and only proceed if the transaction succeeds.
|
||||
return player.getResinManager().useResin(resinCost);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<GameItem> rollRewards(boolean useCondensed) {
|
||||
List<GameItem> rewards = new ArrayList<>();
|
||||
int dungeonId = this.dungeonData.getId();
|
||||
// If we have specific drop data for this dungeon, we use it.
|
||||
if (GameData.getDungeonDropDataMap().containsKey(dungeonId)) {
|
||||
List<DungeonDropEntry> dropEntries = GameData.getDungeonDropDataMap().get(dungeonId);
|
||||
|
||||
// Roll for each drop group.
|
||||
for (var entry : dropEntries) {
|
||||
// Determine the number of drops we get for this entry.
|
||||
int start = entry.getCounts().get(0);
|
||||
int end = entry.getCounts().get(entry.getCounts().size() - 1);
|
||||
var candidateAmounts = IntStream.range(start, end + 1).boxed().collect(Collectors.toList());
|
||||
|
||||
int amount = Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities());
|
||||
|
||||
if (useCondensed) {
|
||||
amount += Utils.drawRandomListElement(candidateAmounts, entry.getProbabilities());
|
||||
}
|
||||
|
||||
// Double rewards in multiplay mode, if specified.
|
||||
if (entry.isMpDouble() && this.getScene().getPlayerCount() > 1) {
|
||||
amount *= 2;
|
||||
}
|
||||
|
||||
// Roll items for this group.
|
||||
// Here, we have to handle stacking, or the client will not display results correctly.
|
||||
// For now, we use the following logic: If the possible drop item are a list of multiple
|
||||
// items,
|
||||
// we roll them separately. If not, we stack them. This should work out in practice, at
|
||||
// least
|
||||
// for the currently existing set of dungeons.
|
||||
if (entry.getItems().size() == 1) {
|
||||
rewards.add(new GameItem(entry.getItems().get(0), amount));
|
||||
} else {
|
||||
for (int i = 0; i < amount; i++) {
|
||||
// int itemIndex = ThreadLocalRandom.current().nextInt(0, entry.getItems().size());
|
||||
// int itemId = entry.getItems().get(itemIndex);
|
||||
int itemId =
|
||||
Utils.drawRandomListElement(entry.getItems(), entry.getItemProbabilities());
|
||||
rewards.add(new GameItem(itemId, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise, we fall back to the preview data.
|
||||
else {
|
||||
Grasscutter.getLogger()
|
||||
.info("No drop data found or dungeon {}, falling back to preview data ...", dungeonId);
|
||||
for (ItemParamData param : dungeonData.getRewardPreviewData().getPreviewItems()) {
|
||||
rewards.add(new GameItem(param.getId(), Math.max(param.getCount(), 1)));
|
||||
}
|
||||
}
|
||||
|
||||
return rewards;
|
||||
}
|
||||
|
||||
public void applyTrialTeam(Player player) {
|
||||
if (getDungeonData() == null) return;
|
||||
|
||||
switch (getDungeonData().getType()) {
|
||||
// case DUNGEON_PLOT is handled by quest execs
|
||||
case DUNGEON_ACTIVITY -> {
|
||||
switch (getDungeonData().getPlayType()) {
|
||||
case DUNGEON_PLAY_TYPE_TRIAL_AVATAR -> {
|
||||
val activityHandler =
|
||||
player
|
||||
.getActivityManager()
|
||||
.getActivityHandlerAs(
|
||||
ActivityType.NEW_ACTIVITY_TRIAL_AVATAR, TrialAvatarActivityHandler.class);
|
||||
activityHandler.ifPresent(
|
||||
trialAvatarActivityHandler ->
|
||||
this.trialTeam = trialAvatarActivityHandler.getTrialAvatarDungeonTeam());
|
||||
}
|
||||
}
|
||||
}
|
||||
case DUNGEON_ELEMENT_CHALLENGE -> {} // TODO
|
||||
}
|
||||
if (this.trialTeam != null) {
|
||||
player.getTeamManager().addTrialAvatars(trialTeam.trialAvatarIds);
|
||||
}
|
||||
}
|
||||
|
||||
public void unsetTrialTeam(Player player) {
|
||||
if (this.trialTeam == null) {
|
||||
return;
|
||||
}
|
||||
player.getTeamManager().removeTrialAvatar();
|
||||
this.trialTeam = null;
|
||||
}
|
||||
|
||||
public void startDungeon() {
|
||||
this.startSceneTime = scene.getSceneTimeSeconds();
|
||||
scene
|
||||
.getPlayers()
|
||||
.forEach(
|
||||
p -> {
|
||||
p.getQuestManager()
|
||||
.queueEvent(QuestContent.QUEST_CONTENT_ENTER_DUNGEON, dungeonData.getId());
|
||||
applyTrialTeam(p);
|
||||
});
|
||||
}
|
||||
|
||||
public void finishDungeon() {
|
||||
notifyEndDungeon(true);
|
||||
endDungeon(BaseDungeonResult.DungeonEndReason.COMPLETED);
|
||||
}
|
||||
|
||||
public void notifyEndDungeon(boolean successfully) {
|
||||
scene
|
||||
.getPlayers()
|
||||
.forEach(
|
||||
p -> {
|
||||
// Quest trigger
|
||||
p.getQuestManager()
|
||||
.queueEvent(
|
||||
successfully
|
||||
? QuestContent.QUEST_CONTENT_FINISH_DUNGEON
|
||||
: QuestContent.QUEST_CONTENT_FAIL_DUNGEON,
|
||||
dungeonData.getId());
|
||||
|
||||
// Battle pass trigger
|
||||
if (dungeonData.getType().isCountsToBattlepass() && successfully) {
|
||||
p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON);
|
||||
}
|
||||
});
|
||||
scene
|
||||
.getScriptManager()
|
||||
.callEvent(new ScriptArgs(0, EventType.EVENT_DUNGEON_SETTLE, successfully ? 1 : 0));
|
||||
}
|
||||
|
||||
public void quitDungeon() {
|
||||
notifyEndDungeon(false);
|
||||
endDungeon(BaseDungeonResult.DungeonEndReason.QUIT);
|
||||
}
|
||||
|
||||
public void failDungeon() {
|
||||
notifyEndDungeon(false);
|
||||
endDungeon(BaseDungeonResult.DungeonEndReason.FAILED);
|
||||
}
|
||||
|
||||
public void endDungeon(BaseDungeonResult.DungeonEndReason endReason) {
|
||||
if (scene.getDungeonSettleListeners() != null) {
|
||||
scene.getDungeonSettleListeners().forEach(o -> o.onDungeonSettle(this, endReason));
|
||||
}
|
||||
ended = true;
|
||||
}
|
||||
|
||||
public void restartDungeon() {
|
||||
this.scene.setKilledMonsterCount(0);
|
||||
this.rewardedPlayers.clear();
|
||||
Arrays.fill(finishedConditions, 0);
|
||||
this.ended = false;
|
||||
this.activeDungeonWayPoints.clear();
|
||||
}
|
||||
|
||||
public void cleanUpScene() {
|
||||
this.scene.setDungeonManager(null);
|
||||
this.scene.setKilledMonsterCount(0);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package emu.grasscutter.game.dungeons;
|
||||
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
|
||||
public interface DungeonSettleListener {
|
||||
void onDungeonSettle(Scene scene);
|
||||
}
|
||||
package emu.grasscutter.game.dungeons;
|
||||
|
||||
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult;
|
||||
|
||||
public interface DungeonSettleListener {
|
||||
void onDungeonSettle(DungeonManager dungeonManager, BaseDungeonResult.DungeonEndReason endReason);
|
||||
}
|
||||
|
||||
@@ -1,115 +1,157 @@
|
||||
package emu.grasscutter.game.dungeons;
|
||||
|
||||
import emu.grasscutter.GameConstants;
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.props.SceneType;
|
||||
import emu.grasscutter.net.packet.BasePacket;
|
||||
import emu.grasscutter.net.packet.PacketOpcodes;
|
||||
import emu.grasscutter.server.game.BaseGameSystem;
|
||||
import emu.grasscutter.server.game.GameServer;
|
||||
import emu.grasscutter.server.packet.send.PacketDungeonEntryInfoRsp;
|
||||
import emu.grasscutter.server.packet.send.PacketPlayerEnterDungeonRsp;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import java.util.List;
|
||||
|
||||
public final class DungeonSystem extends BaseGameSystem {
|
||||
private static final BasicDungeonSettleListener basicDungeonSettleObserver =
|
||||
new BasicDungeonSettleListener();
|
||||
|
||||
public DungeonSystem(GameServer server) {
|
||||
super(server);
|
||||
}
|
||||
|
||||
public void getEntryInfo(Player player, int pointId) {
|
||||
var 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 enterDungeon(Player player, int pointId, int dungeonId) {
|
||||
var data = GameData.getDungeonDataMap().get(dungeonId);
|
||||
if (data == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Grasscutter.getLogger()
|
||||
.debug(
|
||||
"{}({}) is trying to enter dungeon {}",
|
||||
player.getNickname(),
|
||||
player.getUid(),
|
||||
dungeonId);
|
||||
|
||||
var sceneId = data.getSceneId();
|
||||
player.getScene().setPrevScene(sceneId);
|
||||
|
||||
if (player.getWorld().transferPlayerToScene(player, sceneId, data)) {
|
||||
player.getScene().addDungeonSettleObserver(basicDungeonSettleObserver);
|
||||
player.getQuestManager().triggerEvent(QuestContent.QUEST_CONTENT_ENTER_DUNGEON, data.getId());
|
||||
}
|
||||
|
||||
player.getScene().setPrevScenePoint(pointId);
|
||||
player.sendPacket(new PacketPlayerEnterDungeonRsp(pointId, dungeonId));
|
||||
return true;
|
||||
}
|
||||
|
||||
/** used in tower dungeons handoff */
|
||||
public boolean handoffDungeon(
|
||||
Player player, int dungeonId, List<DungeonSettleListener> dungeonSettleListeners) {
|
||||
var data = GameData.getDungeonDataMap().get(dungeonId);
|
||||
if (data == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Grasscutter.getLogger()
|
||||
.debug(
|
||||
"{}({}) 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) {
|
||||
var scene = player.getScene();
|
||||
|
||||
if (scene == null || scene.getSceneType() != SceneType.SCENE_DUNGEON) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get previous scene
|
||||
var prevScene = scene.getPrevScene() > 0 ? scene.getPrevScene() : 3;
|
||||
|
||||
// Get previous position
|
||||
var dungeonData = scene.getDungeonData();
|
||||
var prevPos = new Position(GameConstants.START_POSITION);
|
||||
|
||||
if (dungeonData != null) {
|
||||
var entry = GameData.getScenePointEntryById(prevScene, scene.getPrevScenePoint());
|
||||
|
||||
if (entry != null) {
|
||||
prevPos.set(entry.getPointData().getTranPos());
|
||||
}
|
||||
}
|
||||
|
||||
// clean temp team if it has
|
||||
player.getTeamManager().cleanTemporaryTeam();
|
||||
player.getTowerManager().clearEntry();
|
||||
|
||||
// Transfer player back to world
|
||||
player.getWorld().transferPlayerToScene(player, prevScene, prevPos);
|
||||
player.sendPacket(new BasePacket(PacketOpcodes.PlayerQuitDungeonRsp));
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,38 @@
|
||||
package emu.grasscutter.game.dungeons;
|
||||
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.server.packet.send.PacketDungeonSettleNotify;
|
||||
import emu.grasscutter.server.packet.send.PacketTowerFloorRecordChangeNotify;
|
||||
import emu.grasscutter.utils.Utils;
|
||||
|
||||
public class TowerDungeonSettleListener implements DungeonSettleListener {
|
||||
|
||||
@Override
|
||||
public void onDungeonSettle(Scene scene) {
|
||||
if (scene.getScriptManager().getVariables().containsKey("stage")
|
||||
&& scene.getScriptManager().getVariables().get("stage") == 1) {
|
||||
return;
|
||||
}
|
||||
scene.setAutoCloseTime(Utils.getCurrentSeconds() + 1000);
|
||||
var towerManager = scene.getPlayers().get(0).getTowerManager();
|
||||
|
||||
towerManager.notifyCurLevelRecordChangeWhenDone(3);
|
||||
scene.broadcastPacket(
|
||||
new PacketTowerFloorRecordChangeNotify(
|
||||
towerManager.getCurrentFloorId(), 3, towerManager.canEnterScheduleFloor()));
|
||||
|
||||
scene.broadcastPacket(
|
||||
new PacketDungeonSettleNotify(
|
||||
scene.getChallenge(),
|
||||
towerManager.hasNextFloor(),
|
||||
towerManager.hasNextLevel(),
|
||||
towerManager.hasNextLevel() ? 0 : towerManager.getNextFloorId()));
|
||||
}
|
||||
}
|
||||
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));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,144 +1,167 @@
|
||||
package emu.grasscutter.game.dungeons.challenge;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
|
||||
import emu.grasscutter.game.entity.EntityGadget;
|
||||
import emu.grasscutter.game.entity.EntityMonster;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.scripts.constants.EventType;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
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 (!inProgress()) {
|
||||
return;
|
||||
}
|
||||
finish(true);
|
||||
this.getScene()
|
||||
.getScriptManager()
|
||||
.callEvent(
|
||||
EventType.EVENT_CHALLENGE_SUCCESS,
|
||||
// TODO record the time in PARAM2 and used in action
|
||||
new ScriptArgs().setParam2(finishedTime));
|
||||
|
||||
challengeTriggers.forEach(t -> t.onFinish(this));
|
||||
}
|
||||
|
||||
public void fail() {
|
||||
if (!inProgress()) {
|
||||
return;
|
||||
}
|
||||
finish(false);
|
||||
this.getScene().getScriptManager().callEvent(EventType.EVENT_CHALLENGE_FAIL, null);
|
||||
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 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;
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,36 @@
|
||||
package emu.grasscutter.game.dungeons.challenge.factory;
|
||||
|
||||
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;
|
||||
|
||||
public class ChallengeFactory {
|
||||
|
||||
private static final List<ChallengeFactoryHandler> challengeFactoryHandlers = new ArrayList<>();
|
||||
|
||||
static {
|
||||
challengeFactoryHandlers.add(new DungeonChallengeFactoryHandler());
|
||||
challengeFactoryHandlers.add(new DungeonGuardChallengeFactoryHandler());
|
||||
challengeFactoryHandlers.add(new KillGadgetChallengeFactoryHandler());
|
||||
challengeFactoryHandlers.add(new KillMonsterChallengeFactoryHandler());
|
||||
}
|
||||
|
||||
public static WorldChallenge getChallenge(
|
||||
int param1,
|
||||
int param2,
|
||||
int param3,
|
||||
int param4,
|
||||
int param5,
|
||||
int param6,
|
||||
Scene scene,
|
||||
SceneGroup group) {
|
||||
for (var handler : challengeFactoryHandlers) {
|
||||
if (!handler.isThisType(param1, param2, param3, param4, param5, param6, scene, group)) {
|
||||
continue;
|
||||
}
|
||||
return handler.build(param1, param2, 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 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,11 @@
|
||||
package emu.grasscutter.game.dungeons.challenge.factory;
|
||||
|
||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
|
||||
public interface ChallengeFactoryHandler {
|
||||
boolean isThisType(
|
||||
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);
|
||||
boolean isThisType(ChallengeType challengeType);
|
||||
WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group);
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package emu.grasscutter.game.dungeons.challenge.factory;
|
||||
|
||||
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
|
||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
|
||||
import emu.grasscutter.game.props.SceneType;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
import java.util.List;
|
||||
|
||||
public class DungeonChallengeFactoryHandler implements ChallengeFactoryHandler {
|
||||
@Override
|
||||
public boolean isThisType(
|
||||
int challengeIndex,
|
||||
int challengeId,
|
||||
int param3,
|
||||
int param4,
|
||||
int param5,
|
||||
int param6,
|
||||
Scene scene,
|
||||
SceneGroup group) {
|
||||
// ActiveChallenge with 1,1000,300,233101003,15,0
|
||||
return scene.getSceneType() == SceneType.SCENE_DUNGEON && param4 == group.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldChallenge build(
|
||||
int challengeIndex,
|
||||
int challengeId,
|
||||
int param3,
|
||||
int param4,
|
||||
int param5,
|
||||
int param6,
|
||||
Scene scene,
|
||||
SceneGroup group) {
|
||||
var realGroup = scene.getScriptManager().getGroupById(param4);
|
||||
return new DungeonChallenge(
|
||||
scene,
|
||||
realGroup,
|
||||
challengeId, // Id
|
||||
challengeIndex, // Index
|
||||
List.of(param5, param3),
|
||||
param3, // Limit
|
||||
param5, // Goal
|
||||
List.of(new InTimeTrigger(), new KillMonsterTrigger()));
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
package emu.grasscutter.game.dungeons.challenge.factory;
|
||||
|
||||
import emu.grasscutter.game.dungeons.challenge.DungeonChallenge;
|
||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.GuardTrigger;
|
||||
import emu.grasscutter.game.props.SceneType;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
import java.util.List;
|
||||
|
||||
public class DungeonGuardChallengeFactoryHandler implements ChallengeFactoryHandler {
|
||||
@Override
|
||||
public boolean isThisType(
|
||||
int challengeIndex,
|
||||
int challengeId,
|
||||
int param3,
|
||||
int param4,
|
||||
int param5,
|
||||
int param6,
|
||||
Scene scene,
|
||||
SceneGroup group) {
|
||||
// ActiveChallenge with 1,188,234101003,12,3030,0
|
||||
return scene.getSceneType() == SceneType.SCENE_DUNGEON && param3 == group.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldChallenge build(
|
||||
int challengeIndex,
|
||||
int challengeId,
|
||||
int param3,
|
||||
int param4,
|
||||
int param5,
|
||||
int param6,
|
||||
Scene scene,
|
||||
SceneGroup group) {
|
||||
var realGroup = scene.getScriptManager().getGroupById(param3);
|
||||
return new DungeonChallenge(
|
||||
scene,
|
||||
realGroup,
|
||||
challengeId, // Id
|
||||
challengeIndex, // Index
|
||||
List.of(param4, 0),
|
||||
0, // Limit
|
||||
param4, // Goal
|
||||
List.of(new GuardTrigger()));
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
package emu.grasscutter.game.dungeons.challenge.factory;
|
||||
|
||||
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_KILL_COUNT_GUARD_HP;
|
||||
|
||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.GuardTrigger;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
import java.util.List;
|
||||
import lombok.val;
|
||||
|
||||
public class KillAndGuardChallengeFactoryHandler implements ChallengeFactoryHandler {
|
||||
import java.util.List;
|
||||
|
||||
import static emu.grasscutter.game.dungeons.challenge.enums.ChallengeType.CHALLENGE_KILL_COUNT_GUARD_HP;
|
||||
|
||||
public class KillAndGuardChallengeFactoryHandler implements ChallengeFactoryHandler{
|
||||
@Override
|
||||
public boolean isThisType(ChallengeType challengeType) {
|
||||
// ActiveChallenge with 1,188,234101003,12,3030,0
|
||||
@@ -19,24 +20,15 @@ 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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package emu.grasscutter.game.dungeons.challenge.factory;
|
||||
|
||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.KillGadgetTrigger;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
import java.util.List;
|
||||
|
||||
public class KillGadgetChallengeFactoryHandler implements ChallengeFactoryHandler {
|
||||
@Override
|
||||
public boolean isThisType(
|
||||
int challengeIndex,
|
||||
int challengeId,
|
||||
int param3,
|
||||
int param4,
|
||||
int param5,
|
||||
int param6,
|
||||
Scene scene,
|
||||
SceneGroup group) {
|
||||
// kill gadgets(explosive barrel) in time
|
||||
// ActiveChallenge with 56,201,20,2,201,4
|
||||
// open chest in time
|
||||
// ActiveChallenge with 666,202,30,7,202,1
|
||||
return challengeId == 201 || challengeId == 202;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldChallenge build(
|
||||
int challengeIndex,
|
||||
int challengeId,
|
||||
int param3,
|
||||
int param4,
|
||||
int param5,
|
||||
int param6,
|
||||
Scene scene,
|
||||
SceneGroup group) {
|
||||
return new WorldChallenge(
|
||||
scene,
|
||||
group,
|
||||
challengeId, // Id
|
||||
challengeIndex, // Index
|
||||
List.of(param3, param6, 0),
|
||||
param3, // Limit
|
||||
param6, // Goal
|
||||
List.of(new InTimeTrigger(), new KillGadgetTrigger()));
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package emu.grasscutter.game.dungeons.challenge.factory;
|
||||
|
||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||
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;
|
||||
|
||||
public class KillMonsterChallengeFactoryHandler implements ChallengeFactoryHandler {
|
||||
@Override
|
||||
public boolean isThisType(
|
||||
int challengeIndex,
|
||||
int challengeId,
|
||||
int param3,
|
||||
int param4,
|
||||
int param5,
|
||||
int param6,
|
||||
Scene scene,
|
||||
SceneGroup group) {
|
||||
// ActiveChallenge with 180,180,45,133108061,1,0
|
||||
return challengeId == 180;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldChallenge build(
|
||||
int challengeIndex,
|
||||
int challengeId,
|
||||
int param3,
|
||||
int param4,
|
||||
int param5,
|
||||
int param6,
|
||||
Scene scene,
|
||||
SceneGroup group) {
|
||||
var realGroup = scene.getScriptManager().getGroupById(param4);
|
||||
return new WorldChallenge(
|
||||
scene,
|
||||
realGroup,
|
||||
challengeId, // Id
|
||||
challengeIndex, // Index
|
||||
List.of(param5, param3),
|
||||
param3, // Limit
|
||||
param5, // Goal
|
||||
List.of(new KillMonsterTrigger(), new InTimeTrigger()));
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,11 @@ import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
import java.util.List;
|
||||
import lombok.val;
|
||||
|
||||
public class KillMonsterCountChallengeFactoryHandler implements ChallengeFactoryHandler {
|
||||
import java.util.List;
|
||||
|
||||
public class KillMonsterCountChallengeFactoryHandler implements ChallengeFactoryHandler{
|
||||
@Override
|
||||
public boolean isThisType(ChallengeType challengeType) {
|
||||
// ActiveChallenge with 1, 1, 241033003, 15, 0, 0
|
||||
@@ -16,24 +17,16 @@ 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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,33 @@
|
||||
package emu.grasscutter.game.dungeons.challenge.factory;
|
||||
|
||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
import java.util.List;
|
||||
import lombok.val;
|
||||
|
||||
public class KillMonsterInTimeChallengeFactoryHandler implements ChallengeFactoryHandler {
|
||||
@Override
|
||||
public boolean isThisType(ChallengeType challengeType) {
|
||||
// ActiveChallenge with 180, 72, 240, 133220161, 133220161, 0
|
||||
return challengeType == ChallengeType.CHALLENGE_KILL_MONSTER_IN_TIME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldChallenge build(
|
||||
int challengeIndex,
|
||||
int challengeId,
|
||||
int timeLimit,
|
||||
int groupId,
|
||||
int targetCfgId,
|
||||
int param6,
|
||||
Scene scene,
|
||||
SceneGroup group) {
|
||||
val realGroup = scene.getScriptManager().getGroupById(groupId);
|
||||
return new WorldChallenge(
|
||||
scene,
|
||||
realGroup,
|
||||
challengeId, // Id
|
||||
challengeIndex, // Index
|
||||
List.of(timeLimit),
|
||||
timeLimit, // Limit
|
||||
0, // Goal
|
||||
List.of(new KillMonsterTrigger(targetCfgId), new InTimeTrigger()));
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.game.dungeons.challenge.factory;
|
||||
|
||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||
import emu.grasscutter.game.dungeons.challenge.enums.ChallengeType;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
import 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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,37 +6,30 @@ import emu.grasscutter.game.dungeons.challenge.trigger.InTimeTrigger;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterCountTrigger;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.scripts.data.SceneGroup;
|
||||
import java.util.List;
|
||||
import lombok.val;
|
||||
|
||||
public class KillMonsterTimeChallengeFactoryHandler implements ChallengeFactoryHandler {
|
||||
import java.util.List;
|
||||
|
||||
public class KillMonsterTimeChallengeFactoryHandler implements ChallengeFactoryHandler{
|
||||
@Override
|
||||
public boolean isThisType(ChallengeType challengeType) {
|
||||
// ActiveChallenge with 180,180,45,133108061,1,0
|
||||
// ActiveChallenge Fast with 1001, 5, 15, 240004005, 10, 0
|
||||
return challengeType == ChallengeType.CHALLENGE_KILL_COUNT_IN_TIME
|
||||
|| challengeType == ChallengeType.CHALLENGE_KILL_COUNT_FAST;
|
||||
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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
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) {
|
||||
@@ -18,23 +19,15 @@ 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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
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) {
|
||||
@@ -21,23 +22,15 @@ 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)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
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;
|
||||
|
||||
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) {}
|
||||
}
|
||||
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) { }
|
||||
}
|
||||
|
||||
@@ -1,26 +1,38 @@
|
||||
package emu.grasscutter.game.dungeons.challenge.trigger;
|
||||
|
||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||
import emu.grasscutter.game.entity.EntityGadget;
|
||||
import emu.grasscutter.game.props.FightProperty;
|
||||
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
|
||||
|
||||
public class GuardTrigger extends KillMonsterTrigger {
|
||||
@Override
|
||||
public void onBegin(WorldChallenge challenge) {
|
||||
super.onBegin(challenge);
|
||||
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, 100));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) {
|
||||
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);
|
||||
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 2, 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.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
package emu.grasscutter.game.dungeons.challenge.trigger;
|
||||
|
||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||
import emu.grasscutter.game.entity.EntityMonster;
|
||||
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
|
||||
|
||||
public class KillMonsterTrigger extends ChallengeTrigger {
|
||||
@Override
|
||||
public void onBegin(WorldChallenge challenge) {
|
||||
challenge
|
||||
.getScene()
|
||||
.broadcastPacket(new PacketChallengeDataNotify(challenge, 1, challenge.getScore().get()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {
|
||||
var newScore = challenge.increaseScore();
|
||||
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 1, newScore));
|
||||
|
||||
if (newScore >= challenge.getGoal()) {
|
||||
challenge.done();
|
||||
}
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.game.dungeons.challenge.trigger;
|
||||
|
||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||
import emu.grasscutter.game.entity.EntityMonster;
|
||||
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,347 +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);
|
||||
|
||||
GameItem weapon = getAvatar().getWeapon();
|
||||
if (weapon != null) {
|
||||
weapon.setWeaponEntityId(getScene().getWorld().getNextEntityId(EntityIdType.WEAPON));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,63 @@
|
||||
package emu.grasscutter.game.entity;
|
||||
|
||||
import emu.grasscutter.data.binout.ConfigGadget;
|
||||
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 void onDeath(int killerId) {
|
||||
super.onDeath(killerId); // Invoke super class's onDeath() method.
|
||||
}
|
||||
|
||||
protected void fillFightProps(ConfigGadget 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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,206 +1,281 @@
|
||||
package emu.grasscutter.game.entity;
|
||||
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.binout.ConfigGadget;
|
||||
import emu.grasscutter.data.excels.GadgetData;
|
||||
import emu.grasscutter.game.entity.gadget.*;
|
||||
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.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.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.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.utils.Position;
|
||||
import emu.grasscutter.utils.ProtoHelper;
|
||||
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
|
||||
import java.util.Optional;
|
||||
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, lazy = true)
|
||||
private final Int2FloatMap fightProperties = new Int2FloatOpenHashMap();
|
||||
|
||||
@Getter(onMethod_ = @Override)
|
||||
@Setter
|
||||
private int gadgetId;
|
||||
|
||||
@Getter @Setter private int state;
|
||||
@Getter @Setter private int pointType;
|
||||
@Getter private GadgetContent content;
|
||||
@Getter @Setter private SceneGadget metaGadget;
|
||||
@Nullable @Getter private final ConfigGadget configGadget;
|
||||
|
||||
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);
|
||||
this.configGadget =
|
||||
Optional.ofNullable(this.gadgetData)
|
||||
.map(GadgetData::getJsonName)
|
||||
.map(GameData.getGadgetConfigData()::get)
|
||||
.orElse(null);
|
||||
this.id = this.getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
|
||||
this.gadgetId = gadgetId;
|
||||
this.content = content;
|
||||
fillFightProps(configGadget);
|
||||
}
|
||||
|
||||
public void updateState(int state) {
|
||||
this.setState(state);
|
||||
this.getScene().broadcastPacket(new PacketGadgetStateNotify(this, state));
|
||||
getScene()
|
||||
.getScriptManager()
|
||||
.callEvent(EventType.EVENT_GADGET_STATE_CHANGE, new ScriptArgs(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 -> 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(EventType.EVENT_GADGET_CREATE, new ScriptArgs(this.getConfigId()));
|
||||
}
|
||||
|
||||
@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(EventType.EVENT_ANY_GADGET_DIE, new ScriptArgs(this.getConfigId()));
|
||||
}
|
||||
|
||||
@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_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 (this.getContent() != null) {
|
||||
this.getContent().onBuildProto(gadgetInfo);
|
||||
}
|
||||
|
||||
entityInfo.setGadget(gadgetInfo);
|
||||
|
||||
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.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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,260 +1,265 @@
|
||||
package emu.grasscutter.game.entity;
|
||||
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.common.PropGrowCurve;
|
||||
import emu.grasscutter.data.excels.EnvAnimalGatherConfigData;
|
||||
import emu.grasscutter.data.excels.monster.MonsterCurveData;
|
||||
import emu.grasscutter.data.excels.monster.MonsterData;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.props.*;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
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.MonsterBornTypeOuterClass.MonsterBornType;
|
||||
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.SceneMonsterInfoOuterClass.SceneMonsterInfo;
|
||||
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
|
||||
import emu.grasscutter.scripts.constants.EventType;
|
||||
import emu.grasscutter.scripts.data.ScriptArgs;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import emu.grasscutter.utils.ProtoHelper;
|
||||
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
|
||||
import java.util.Optional;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class EntityMonster extends GameEntity {
|
||||
@Getter private final MonsterData monsterData;
|
||||
|
||||
@Getter(onMethod_ = @Override)
|
||||
private final Int2FloatOpenHashMap fightProperties;
|
||||
|
||||
@Getter(onMethod_ = @Override)
|
||||
private final Position position;
|
||||
|
||||
@Getter(onMethod_ = @Override)
|
||||
private final Position rotation;
|
||||
|
||||
@Getter private final Position bornPos;
|
||||
@Getter private final int level;
|
||||
private int weaponEntityId;
|
||||
@Getter @Setter private int poseId;
|
||||
@Getter @Setter private int aiId = -1;
|
||||
|
||||
public EntityMonster(Scene scene, MonsterData monsterData, Position pos, int level) {
|
||||
super(scene);
|
||||
this.id = getWorld().getNextEntityId(EntityIdType.MONSTER);
|
||||
this.monsterData = monsterData;
|
||||
this.fightProperties = new Int2FloatOpenHashMap();
|
||||
this.position = new Position(pos);
|
||||
this.rotation = new Position();
|
||||
this.bornPos = getPosition().clone();
|
||||
this.level = level;
|
||||
|
||||
// Monster weapon
|
||||
if (getMonsterWeaponId() > 0) {
|
||||
this.weaponEntityId = getWorld().getNextEntityId(EntityIdType.WEAPON);
|
||||
}
|
||||
|
||||
this.recalcStats();
|
||||
}
|
||||
|
||||
public int getMonsterWeaponId() {
|
||||
return this.getMonsterData().getWeaponId();
|
||||
}
|
||||
|
||||
private int getMonsterId() {
|
||||
return this.getMonsterData().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInteract(Player player, GadgetInteractReq interactReq) {
|
||||
EnvAnimalGatherConfigData gatherData =
|
||||
GameData.getEnvAnimalGatherConfigDataMap().get(this.getMonsterData().getId());
|
||||
|
||||
if (gatherData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.getInventory().addItem(gatherData.getGatherItem(), ActionReason.SubfieldDrop);
|
||||
|
||||
this.getScene().killEntity(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
// Lua event
|
||||
getScene()
|
||||
.getScriptManager()
|
||||
.callEvent(EventType.EVENT_ANY_MONSTER_LIVE, new ScriptArgs(this.getConfigId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void damage(float amount, int killerId) {
|
||||
// Get HP before damage.
|
||||
float hpBeforeDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||
|
||||
// Apply damage.
|
||||
super.damage(amount, killerId);
|
||||
|
||||
// Get HP after damage.
|
||||
float hpAfterDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||
|
||||
// Invoke energy drop logic.
|
||||
for (Player player : this.getScene().getPlayers()) {
|
||||
player.getEnergyManager().handleMonsterEnergyDrop(this, hpBeforeDamage, hpAfterDamage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeath(int killerId) {
|
||||
super.onDeath(killerId); // Invoke super class's onDeath() method.
|
||||
var scene = this.getScene();
|
||||
var challenge = Optional.ofNullable(scene.getChallenge());
|
||||
var scriptManager = scene.getScriptManager();
|
||||
|
||||
Optional.ofNullable(this.getSpawnEntry()).ifPresent(scene.getDeadSpawnedEntities()::add);
|
||||
|
||||
// first set the challenge data
|
||||
challenge.ifPresent(c -> c.onMonsterDeath(this));
|
||||
|
||||
if (scriptManager.isInit() && this.getGroupId() > 0) {
|
||||
Optional.ofNullable(scriptManager.getScriptMonsterSpawnService())
|
||||
.ifPresent(s -> s.onMonsterDead(this));
|
||||
|
||||
// prevent spawn monster after success
|
||||
if (challenge.map(c -> c.inProgress()).orElse(true))
|
||||
scriptManager.callEvent(
|
||||
EventType.EVENT_ANY_MONSTER_DIE, new ScriptArgs().setParam1(this.getConfigId()));
|
||||
}
|
||||
// Battle Pass trigger
|
||||
scene
|
||||
.getPlayers()
|
||||
.forEach(
|
||||
p ->
|
||||
p.getBattlePassManager()
|
||||
.triggerMission(
|
||||
WatcherTriggerType.TRIGGER_MONSTER_DIE, this.getMonsterId(), 1));
|
||||
}
|
||||
|
||||
public void recalcStats() {
|
||||
// Monster data
|
||||
MonsterData data = this.getMonsterData();
|
||||
|
||||
// Get hp percent, set to 100% if none
|
||||
float hpPercent =
|
||||
this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) <= 0
|
||||
? 1f
|
||||
: this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP)
|
||||
/ this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
||||
|
||||
// Clear properties
|
||||
this.getFightProperties().clear();
|
||||
|
||||
// Base stats
|
||||
MonsterData.definedFightProperties.forEach(
|
||||
prop -> this.setFightProperty(prop, data.getFightProperty(prop)));
|
||||
|
||||
// Level curve
|
||||
MonsterCurveData curve = GameData.getMonsterCurveDataMap().get(this.getLevel());
|
||||
if (curve != null) {
|
||||
for (PropGrowCurve growCurve : data.getPropGrowCurves()) {
|
||||
FightProperty prop = FightProperty.getPropByName(growCurve.getType());
|
||||
this.setFightProperty(
|
||||
prop, this.getFightProperty(prop) * curve.getMultByProp(growCurve.getGrowCurve()));
|
||||
}
|
||||
}
|
||||
|
||||
// Set % stats
|
||||
FightProperty.forEachCompoundProperty(
|
||||
c ->
|
||||
this.setFightProperty(
|
||||
c.getResult(),
|
||||
this.getFightProperty(c.getFlat())
|
||||
+ (this.getFightProperty(c.getBase())
|
||||
* (1f + this.getFightProperty(c.getPercent())))));
|
||||
|
||||
// Set current hp
|
||||
this.setFightProperty(
|
||||
FightProperty.FIGHT_PROP_CUR_HP,
|
||||
this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SceneEntityInfo toProto() {
|
||||
var authority =
|
||||
EntityAuthorityInfo.newBuilder()
|
||||
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
|
||||
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
|
||||
.setAiInfo(
|
||||
SceneEntityAiInfo.newBuilder()
|
||||
.setIsAiOpen(true)
|
||||
.setBornPos(this.getBornPos().toProto()))
|
||||
.setBornPos(this.getBornPos().toProto())
|
||||
.build();
|
||||
|
||||
var entityInfo =
|
||||
SceneEntityInfo.newBuilder()
|
||||
.setEntityId(getId())
|
||||
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_MONSTER)
|
||||
.setMotionInfo(this.getMotionInfo())
|
||||
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
|
||||
.setEntityClientData(EntityClientData.newBuilder())
|
||||
.setEntityAuthorityInfo(authority)
|
||||
.setLifeState(this.getLifeState().getValue());
|
||||
|
||||
this.addAllFightPropsToEntityInfo(entityInfo);
|
||||
|
||||
entityInfo.addPropList(
|
||||
PropPair.newBuilder()
|
||||
.setType(PlayerProperty.PROP_LEVEL.getId())
|
||||
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getLevel()))
|
||||
.build());
|
||||
|
||||
var monsterInfo =
|
||||
SceneMonsterInfo.newBuilder()
|
||||
.setMonsterId(getMonsterId())
|
||||
.setGroupId(this.getGroupId())
|
||||
.setConfigId(this.getConfigId())
|
||||
.addAllAffixList(getMonsterData().getAffix())
|
||||
.setAuthorityPeerId(getWorld().getHostPeerId())
|
||||
.setPoseId(this.getPoseId())
|
||||
.setBlockId(3001)
|
||||
.setBornType(MonsterBornType.MONSTER_BORN_TYPE_DEFAULT)
|
||||
.setSpecialNameId(40);
|
||||
|
||||
if (getMonsterData().getDescribeData() != null) {
|
||||
monsterInfo.setTitleId(getMonsterData().getDescribeData().getTitleID());
|
||||
}
|
||||
|
||||
if (this.getMonsterWeaponId() > 0) {
|
||||
SceneWeaponInfo weaponInfo =
|
||||
SceneWeaponInfo.newBuilder()
|
||||
.setEntityId(this.weaponEntityId)
|
||||
.setGadgetId(this.getMonsterWeaponId())
|
||||
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
|
||||
.build();
|
||||
|
||||
monsterInfo.addWeaponList(weaponInfo);
|
||||
}
|
||||
if (this.aiId != -1) {
|
||||
monsterInfo.setAiConfigId(aiId);
|
||||
}
|
||||
|
||||
entityInfo.setMonster(monsterInfo);
|
||||
|
||||
return entityInfo.build();
|
||||
}
|
||||
}
|
||||
package emu.grasscutter.game.entity;
|
||||
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.common.PropGrowCurve;
|
||||
import emu.grasscutter.data.excels.EnvAnimalGatherConfigData;
|
||||
import emu.grasscutter.data.excels.monster.MonsterCurveData;
|
||||
import emu.grasscutter.data.excels.monster.MonsterData;
|
||||
import emu.grasscutter.game.dungeons.enums.DungeonPassConditionType;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.props.*;
|
||||
import emu.grasscutter.game.quest.enums.QuestContent;
|
||||
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.MonsterBornTypeOuterClass.MonsterBornType;
|
||||
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.SceneMonsterInfoOuterClass.SceneMonsterInfo;
|
||||
import emu.grasscutter.net.proto.SceneWeaponInfoOuterClass.SceneWeaponInfo;
|
||||
import emu.grasscutter.scripts.constants.EventType;
|
||||
import emu.grasscutter.scripts.data.SceneMonster;
|
||||
import emu.grasscutter.scripts.data.ScriptArgs;
|
||||
import emu.grasscutter.server.event.entity.EntityDamageEvent;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import emu.grasscutter.utils.ProtoHelper;
|
||||
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static emu.grasscutter.scripts.constants.EventType.EVENT_SPECIFIC_MONSTER_HP_CHANGE;
|
||||
|
||||
public class EntityMonster extends GameEntity {
|
||||
@Getter(onMethod = @__(@Override))
|
||||
private final Int2FloatOpenHashMap fightProperties;
|
||||
|
||||
@Getter(onMethod = @__(@Override))
|
||||
private final Position position;
|
||||
@Getter(onMethod = @__(@Override))
|
||||
private final Position rotation;
|
||||
@Getter private final MonsterData monsterData;
|
||||
@Getter private final Position bornPos;
|
||||
@Getter private final int level;
|
||||
@Getter private int weaponEntityId;
|
||||
@Getter @Setter private int poseId;
|
||||
@Getter @Setter private int aiId = -1;
|
||||
|
||||
@Getter @Setter private SceneMonster metaMonster;
|
||||
|
||||
public EntityMonster(Scene scene, MonsterData monsterData, Position pos, int level) {
|
||||
super(scene);
|
||||
this.id = getWorld().getNextEntityId(EntityIdType.MONSTER);
|
||||
this.monsterData = monsterData;
|
||||
this.fightProperties = new Int2FloatOpenHashMap();
|
||||
this.position = new Position(pos);
|
||||
this.rotation = new Position();
|
||||
this.bornPos = getPosition().clone();
|
||||
this.level = level;
|
||||
|
||||
// Monster weapon
|
||||
if (getMonsterWeaponId() > 0) {
|
||||
this.weaponEntityId = getWorld().getNextEntityId(EntityIdType.WEAPON);
|
||||
}
|
||||
|
||||
this.recalcStats();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEntityTypeId() {
|
||||
return getMonsterId();
|
||||
}
|
||||
|
||||
public int getMonsterWeaponId() {
|
||||
return this.getMonsterData().getWeaponId();
|
||||
}
|
||||
|
||||
private int getMonsterId() {
|
||||
return this.getMonsterData().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
return this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) > 0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInteract(Player player, GadgetInteractReq interactReq) {
|
||||
EnvAnimalGatherConfigData gatherData = GameData.getEnvAnimalGatherConfigDataMap().get(this.getMonsterData().getId());
|
||||
|
||||
if (gatherData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.getInventory().addItem(gatherData.getGatherItem(), ActionReason.SubfieldDrop);
|
||||
|
||||
this.getScene().killEntity(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
// Lua event
|
||||
getScene().getScriptManager().callEvent(new ScriptArgs(this.getGroupId(), EventType.EVENT_ANY_MONSTER_LIVE, this.getConfigId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void damage(float amount, int killerId, ElementType attackType) {
|
||||
// Get HP before damage.
|
||||
float hpBeforeDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||
|
||||
// Apply damage.
|
||||
super.damage(amount, killerId, attackType);
|
||||
|
||||
// Get HP after damage.
|
||||
float hpAfterDamage = this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP);
|
||||
|
||||
// Invoke energy drop logic.
|
||||
for (Player player : this.getScene().getPlayers()) {
|
||||
player.getEnergyManager().handleMonsterEnergyDrop(this, hpBeforeDamage, hpAfterDamage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runLuaCallbacks(EntityDamageEvent event) {
|
||||
super.runLuaCallbacks(event);
|
||||
getScene().getScriptManager().callEvent(new ScriptArgs(this.getGroupId(), EVENT_SPECIFIC_MONSTER_HP_CHANGE, getConfigId(), monsterData.getId())
|
||||
.setSourceEntityId(getId())
|
||||
.setParam3((int) this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP))
|
||||
.setEventSource(Integer.toString(getConfigId())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeath(int killerId) {
|
||||
super.onDeath(killerId); // Invoke super class's onDeath() method.
|
||||
var scene = this.getScene();
|
||||
var challenge = Optional.ofNullable(scene.getChallenge());
|
||||
var scriptManager = scene.getScriptManager();
|
||||
|
||||
Optional.ofNullable(this.getSpawnEntry()).ifPresent(scene.getDeadSpawnedEntities()::add);
|
||||
|
||||
// first set the challenge data
|
||||
challenge.ifPresent(c -> c.onMonsterDeath(this));
|
||||
|
||||
if (scriptManager.isInit() && this.getGroupId() > 0) {
|
||||
Optional.ofNullable(scriptManager.getScriptMonsterSpawnService()).ifPresent(s -> s.onMonsterDead(this));
|
||||
|
||||
// prevent spawn monster after success
|
||||
/*if (challenge.map(c -> c.inProgress()).orElse(true)) {
|
||||
scriptManager.callEvent(new ScriptArgs(EventType.EVENT_ANY_MONSTER_DIE, this.getConfigId()).setGroupId(this.getGroupId()));
|
||||
} else if (getScene().getChallenge() == null) {
|
||||
}*/
|
||||
scriptManager.callEvent(new ScriptArgs(this.getGroupId(), EventType.EVENT_ANY_MONSTER_DIE, this.getConfigId()));
|
||||
}
|
||||
// Battle Pass trigger
|
||||
scene.getPlayers().forEach(p -> p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_MONSTER_DIE, this.getMonsterId(), 1));
|
||||
|
||||
scene.getPlayers().forEach(p -> p.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_MONSTER_DIE, this.getMonsterId()));
|
||||
scene.getPlayers().forEach(p -> p.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_KILL_MONSTER, this.getMonsterId()));
|
||||
scene.getPlayers().forEach(p -> p.getQuestManager().queueEvent(QuestContent.QUEST_CONTENT_CLEAR_GROUP_MONSTER, this.getGroupId()));
|
||||
|
||||
SceneGroupInstance groupInstance = scene.getScriptManager().getGroupInstanceById(this.getGroupId());
|
||||
if(groupInstance != null && metaMonster != null)
|
||||
groupInstance.getDeadEntities().add(metaMonster.config_id);
|
||||
|
||||
scene.triggerDungeonEvent(DungeonPassConditionType.DUNGEON_COND_KILL_GROUP_MONSTER, this.getGroupId());
|
||||
scene.triggerDungeonEvent(DungeonPassConditionType.DUNGEON_COND_KILL_TYPE_MONSTER, this.getMonsterData().getType().getValue());
|
||||
scene.triggerDungeonEvent(DungeonPassConditionType.DUNGEON_COND_KILL_MONSTER, this.getMonsterId());
|
||||
}
|
||||
|
||||
public void recalcStats() {
|
||||
// Monster data
|
||||
MonsterData data = this.getMonsterData();
|
||||
|
||||
// Get hp percent, set to 100% if none
|
||||
float hpPercent = this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) <= 0 ? 1f : this.getFightProperty(FightProperty.FIGHT_PROP_CUR_HP) / this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP);
|
||||
|
||||
// Clear properties
|
||||
this.getFightProperties().clear();
|
||||
|
||||
// Base stats
|
||||
MonsterData.definedFightProperties.forEach(prop -> this.setFightProperty(prop, data.getFightProperty(prop)));
|
||||
|
||||
// Level curve
|
||||
MonsterCurveData curve = GameData.getMonsterCurveDataMap().get(this.getLevel());
|
||||
if (curve != null) {
|
||||
for (PropGrowCurve growCurve : data.getPropGrowCurves()) {
|
||||
FightProperty prop = FightProperty.getPropByName(growCurve.getType());
|
||||
this.setFightProperty(prop, this.getFightProperty(prop) * curve.getMultByProp(growCurve.getGrowCurve()));
|
||||
}
|
||||
}
|
||||
|
||||
// Set % stats
|
||||
FightProperty.forEachCompoundProperty(c -> this.setFightProperty(c.getResult(),
|
||||
this.getFightProperty(c.getFlat()) + (this.getFightProperty(c.getBase()) * (1f + this.getFightProperty(c.getPercent())))));
|
||||
|
||||
// Set current hp
|
||||
this.setFightProperty(FightProperty.FIGHT_PROP_CUR_HP, this.getFightProperty(FightProperty.FIGHT_PROP_MAX_HP) * hpPercent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SceneEntityInfo toProto() {
|
||||
var authority = EntityAuthorityInfo.newBuilder()
|
||||
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
|
||||
.setRendererChangedInfo(EntityRendererChangedInfo.newBuilder())
|
||||
.setAiInfo(SceneEntityAiInfo.newBuilder().setIsAiOpen(true).setBornPos(this.getBornPos().toProto()))
|
||||
.setBornPos(this.getBornPos().toProto())
|
||||
.build();
|
||||
|
||||
var entityInfo = SceneEntityInfo.newBuilder()
|
||||
.setEntityId(getId())
|
||||
.setEntityType(ProtEntityType.PROT_ENTITY_TYPE_MONSTER)
|
||||
.setMotionInfo(this.getMotionInfo())
|
||||
.addAnimatorParaList(AnimatorParameterValueInfoPair.newBuilder())
|
||||
.setEntityClientData(EntityClientData.newBuilder())
|
||||
.setEntityAuthorityInfo(authority)
|
||||
.setLifeState(this.getLifeState().getValue());
|
||||
|
||||
this.addAllFightPropsToEntityInfo(entityInfo);
|
||||
|
||||
entityInfo.addPropList(PropPair.newBuilder()
|
||||
.setType(PlayerProperty.PROP_LEVEL.getId())
|
||||
.setPropValue(ProtoHelper.newPropValue(PlayerProperty.PROP_LEVEL, getLevel()))
|
||||
.build());
|
||||
|
||||
var monsterInfo = SceneMonsterInfo.newBuilder()
|
||||
.setMonsterId(getMonsterId())
|
||||
.setGroupId(this.getGroupId())
|
||||
.setConfigId(this.getConfigId())
|
||||
.addAllAffixList(getMonsterData().getAffix())
|
||||
.setAuthorityPeerId(getWorld().getHostPeerId())
|
||||
.setPoseId(this.getPoseId())
|
||||
.setBlockId(getScene().getId())
|
||||
.setBornType(MonsterBornType.MONSTER_BORN_TYPE_DEFAULT);
|
||||
|
||||
if (getMonsterData().getDescribeData() != null) {
|
||||
monsterInfo.setTitleId(getMonsterData().getDescribeData().getTitleId())
|
||||
.setSpecialNameId(getMonsterData().getSpecialNameId());
|
||||
|
||||
}
|
||||
|
||||
if (this.getMonsterWeaponId() > 0) {
|
||||
SceneWeaponInfo weaponInfo = SceneWeaponInfo.newBuilder()
|
||||
.setEntityId(this.weaponEntityId)
|
||||
.setGadgetId(this.getMonsterWeaponId())
|
||||
.setAbilityInfo(AbilitySyncStateInfo.newBuilder())
|
||||
.build();
|
||||
|
||||
monsterInfo.addWeaponList(weaponInfo);
|
||||
}
|
||||
if (this.aiId != -1) {
|
||||
monsterInfo.setAiConfigId(aiId);
|
||||
}
|
||||
|
||||
entityInfo.setMonster(monsterInfo);
|
||||
|
||||
return entityInfo.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +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 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,90 +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);
|
||||
setGroupId(region.group.id);
|
||||
setBlockId(region.group.block_id);
|
||||
setConfigId(region.config_id);
|
||||
this.position = region.pos.clone();
|
||||
this.entities = ConcurrentHashMap.newKeySet();
|
||||
this.metaRegion = region;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,30 @@
|
||||
package emu.grasscutter.game.entity;
|
||||
|
||||
import emu.grasscutter.game.entity.platform.EntityPlatform;
|
||||
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 EntityPlatform 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(), getOwner(), 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,125 +1,112 @@
|
||||
package emu.grasscutter.game.entity;
|
||||
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.binout.ConfigGadget;
|
||||
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 ConfigGadget 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(ConfigGadget 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 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,230 +1,264 @@
|
||||
package emu.grasscutter.game.entity;
|
||||
|
||||
import emu.grasscutter.game.player.Player;
|
||||
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.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;
|
||||
|
||||
// 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 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() {
|
||||
MotionInfo proto =
|
||||
MotionInfo.newBuilder()
|
||||
.setPos(this.getPosition().toProto())
|
||||
.setRot(this.getRotation().toProto())
|
||||
.setSpeed(Vector.newBuilder())
|
||||
.setState(this.getMotionState())
|
||||
.build();
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public void damage(float amount, int killerId) {
|
||||
// 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, 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1,30 +1,33 @@
|
||||
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.SceneGadgetInfoOuterClass.SceneGadgetInfo;
|
||||
import emu.grasscutter.server.packet.send.PacketGadgetInteractRsp;
|
||||
|
||||
public class GadgetRewardStatue extends GadgetContent {
|
||||
|
||||
public GadgetRewardStatue(EntityGadget gadget) {
|
||||
super(gadget);
|
||||
}
|
||||
|
||||
public boolean onInteract(Player player, GadgetInteractReq req) {
|
||||
if (player.getScene().getChallenge() != null
|
||||
&& player.getScene().getChallenge() instanceof DungeonChallenge dungeonChallenge) {
|
||||
dungeonChallenge.getStatueDrops(player, req);
|
||||
}
|
||||
|
||||
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) {}
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
package emu.grasscutter.game.entity.platform;
|
||||
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.binout.ConfigGadget;
|
||||
import emu.grasscutter.data.excels.GadgetData;
|
||||
import emu.grasscutter.game.entity.EntityBaseGadget;
|
||||
import emu.grasscutter.game.entity.EntityClientGadget;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.props.EntityIdType;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.net.proto.*;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import it.unimi.dsi.fastutil.ints.Int2FloatMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2FloatOpenHashMap;
|
||||
import javax.annotation.Nullable;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class EntityPlatform extends EntityBaseGadget {
|
||||
@Getter private final Player owner;
|
||||
|
||||
@Getter(onMethod_ = @Override)
|
||||
private final int gadgetId;
|
||||
|
||||
@Getter private final EntityClientGadget gadget;
|
||||
|
||||
@Getter(onMethod_ = @Override)
|
||||
private final Int2FloatMap fightProperties;
|
||||
|
||||
@Getter private final MovingPlatformTypeOuterClass.MovingPlatformType movingPlatformType;
|
||||
@Nullable @Getter private ConfigGadget configGadget;
|
||||
@Getter @Setter private boolean isStarted;
|
||||
@Getter @Setter private boolean isActive;
|
||||
|
||||
public EntityPlatform(
|
||||
EntityClientGadget gadget,
|
||||
Scene scene,
|
||||
Player player,
|
||||
int gadgetId,
|
||||
Position pos,
|
||||
Position rot,
|
||||
MovingPlatformTypeOuterClass.MovingPlatformType movingPlatformType) {
|
||||
super(scene, pos, rot);
|
||||
this.gadget = gadget;
|
||||
this.owner = player;
|
||||
this.id = getScene().getWorld().getNextEntityId(EntityIdType.GADGET);
|
||||
this.fightProperties = new Int2FloatOpenHashMap();
|
||||
this.movingPlatformType = movingPlatformType;
|
||||
this.gadgetId = gadgetId;
|
||||
GadgetData data = GameData.getGadgetDataMap().get(gadgetId);
|
||||
if (data != null && data.getJsonName() != null) {
|
||||
this.configGadget = GameData.getGadgetConfigData().get(data.getJsonName());
|
||||
}
|
||||
|
||||
fillFightProps(configGadget);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SceneEntityInfoOuterClass.SceneEntityInfo toProto() {
|
||||
var platform =
|
||||
PlatformInfoOuterClass.PlatformInfo.newBuilder()
|
||||
.setMovingPlatformType(movingPlatformType)
|
||||
.build();
|
||||
|
||||
var gadgetInfo =
|
||||
SceneGadgetInfoOuterClass.SceneGadgetInfo.newBuilder()
|
||||
.setGadgetId(getGadgetId())
|
||||
.setAuthorityPeerId(getOwner().getPeerId())
|
||||
.setPlatform(platform);
|
||||
|
||||
var entityInfo =
|
||||
SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder()
|
||||
.setEntityId(getId())
|
||||
.setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_TYPE_GADGET)
|
||||
.setGadget(gadgetInfo)
|
||||
.setLifeState(1);
|
||||
|
||||
this.addAllFightPropsToEntityInfo(entityInfo);
|
||||
return entityInfo.build();
|
||||
}
|
||||
|
||||
public PlatformInfoOuterClass.PlatformInfo onStartRoute() {
|
||||
return PlatformInfoOuterClass.PlatformInfo.newBuilder()
|
||||
.setStartSceneTime(getScene().getSceneTime())
|
||||
.setIsStarted(true)
|
||||
.setPosOffset(getPosition().toProto())
|
||||
.setMovingPlatformType(getMovingPlatformType())
|
||||
.setIsActive(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
public PlatformInfoOuterClass.PlatformInfo onStopRoute() {
|
||||
var sceneTime = getScene().getSceneTime();
|
||||
return PlatformInfoOuterClass.PlatformInfo.newBuilder()
|
||||
.setStartSceneTime(sceneTime)
|
||||
.setStopSceneTime(sceneTime)
|
||||
.setPosOffset(getPosition().toProto())
|
||||
.setMovingPlatformType(getMovingPlatformType())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -1,153 +1,40 @@
|
||||
package emu.grasscutter.game.entity.platform;
|
||||
|
||||
import emu.grasscutter.Grasscutter;
|
||||
import emu.grasscutter.data.binout.ConfigGadget;
|
||||
import emu.grasscutter.game.entity.EntityAvatar;
|
||||
import emu.grasscutter.game.entity.EntitySolarIsotomaClientGadget;
|
||||
import emu.grasscutter.game.entity.GameEntity;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.props.PlayerProperty;
|
||||
import emu.grasscutter.game.world.Scene;
|
||||
import emu.grasscutter.net.proto.*;
|
||||
import emu.grasscutter.server.packet.send.PacketSceneTimeNotify;
|
||||
import emu.grasscutter.utils.Position;
|
||||
import emu.grasscutter.utils.ProtoHelper;
|
||||
|
||||
public class EntitySolarIsotomaElevatorPlatform extends EntityPlatform {
|
||||
public EntitySolarIsotomaElevatorPlatform(
|
||||
EntitySolarIsotomaClientGadget isotoma,
|
||||
Scene scene,
|
||||
Player player,
|
||||
int gadgetId,
|
||||
Position pos,
|
||||
Position rot) {
|
||||
super(
|
||||
isotoma,
|
||||
scene,
|
||||
player,
|
||||
gadgetId,
|
||||
pos,
|
||||
rot,
|
||||
MovingPlatformTypeOuterClass.MovingPlatformType.MOVING_PLATFORM_TYPE_ABILITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void fillFightProps(ConfigGadget 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 ownerAvatar = getScene().getEntityById(getGadget().getOwnerEntityId());
|
||||
if (ownerAvatar != null) {
|
||||
getFightProperties().putAll(ownerAvatar.getFightProperties());
|
||||
return;
|
||||
} else {
|
||||
Grasscutter.getLogger().warn("Why gadget owner is null?");
|
||||
}
|
||||
}
|
||||
|
||||
super.fillFightProps(configGadget);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SceneEntityInfoOuterClass.SceneEntityInfo toProto() {
|
||||
var gadget =
|
||||
SceneGadgetInfoOuterClass.SceneGadgetInfo.newBuilder()
|
||||
.setGadgetId(getGadgetId())
|
||||
.setOwnerEntityId(getGadget().getId())
|
||||
.setAuthorityPeerId(getOwner().getPeerId())
|
||||
.setIsEnableInteract(true)
|
||||
.setAbilityGadget(
|
||||
AbilityGadgetInfoOuterClass.AbilityGadgetInfo.newBuilder()
|
||||
.setCampId(getGadget().getCampId())
|
||||
.setCampTargetType(getGadget().getCampType())
|
||||
.setTargetEntityId(getGadget().getId())
|
||||
.build())
|
||||
.setPlatform(
|
||||
PlatformInfoOuterClass.PlatformInfo.newBuilder()
|
||||
.setStartRot(
|
||||
MathQuaternionOuterClass.MathQuaternion.newBuilder().setW(1.0F).build())
|
||||
.setPosOffset(getGadget().getPosition().toProto())
|
||||
.setRotOffset(
|
||||
MathQuaternionOuterClass.MathQuaternion.newBuilder().setW(1.0F).build())
|
||||
.setMovingPlatformType(
|
||||
MovingPlatformTypeOuterClass.MovingPlatformType
|
||||
.MOVING_PLATFORM_TYPE_ABILITY)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
var authority =
|
||||
EntityAuthorityInfoOuterClass.EntityAuthorityInfo.newBuilder()
|
||||
.setAiInfo(
|
||||
SceneEntityAiInfoOuterClass.SceneEntityAiInfo.newBuilder()
|
||||
.setIsAiOpen(true)
|
||||
.setBornPos(getGadget().getPosition().toProto()))
|
||||
.setBornPos(getGadget().getPosition().toProto())
|
||||
.build();
|
||||
|
||||
var info =
|
||||
SceneEntityInfoOuterClass.SceneEntityInfo.newBuilder()
|
||||
.setEntityType(ProtEntityTypeOuterClass.ProtEntityType.PROT_ENTITY_TYPE_GADGET)
|
||||
.setEntityId(getId())
|
||||
.setMotionInfo(
|
||||
MotionInfoOuterClass.MotionInfo.newBuilder()
|
||||
.setPos(getGadget().getPosition().toProto())
|
||||
.setRot(getGadget().getRotation().toProto())
|
||||
.build());
|
||||
|
||||
GameEntity entity = getScene().getEntityById(getGadget().getOwnerEntityId());
|
||||
if (entity instanceof EntityAvatar avatar) {
|
||||
info.addPropList(
|
||||
PropPairOuterClass.PropPair.newBuilder()
|
||||
.setType(PlayerProperty.PROP_LEVEL.getId())
|
||||
.setPropValue(
|
||||
ProtoHelper.newPropValue(
|
||||
PlayerProperty.PROP_LEVEL, avatar.getAvatar().getLevel()))
|
||||
.build());
|
||||
} else {
|
||||
Grasscutter.getLogger().warn("Why gadget owner doesn't exist?");
|
||||
}
|
||||
|
||||
this.addAllFightPropsToEntityInfo(info);
|
||||
|
||||
info.setLifeState(1).setGadget(gadget).setEntityAuthorityInfo(authority);
|
||||
|
||||
return info.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlatformInfoOuterClass.PlatformInfo onStartRoute() {
|
||||
setStarted(true);
|
||||
setActive(true);
|
||||
|
||||
var sceneTime = getScene().getSceneTime();
|
||||
getOwner().sendPacket(new PacketSceneTimeNotify(getOwner()));
|
||||
|
||||
return PlatformInfoOuterClass.PlatformInfo.newBuilder()
|
||||
.setStartSceneTime(sceneTime + 300)
|
||||
.setIsStarted(true)
|
||||
.setPosOffset(getPosition().toProto())
|
||||
.setRotOffset(MathQuaternionOuterClass.MathQuaternion.newBuilder().setW(1.0F).build())
|
||||
.setMovingPlatformType(getMovingPlatformType())
|
||||
.setIsActive(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlatformInfoOuterClass.PlatformInfo onStopRoute() {
|
||||
setStarted(false);
|
||||
setActive(false);
|
||||
|
||||
return PlatformInfoOuterClass.PlatformInfo.newBuilder()
|
||||
.setStartSceneTime(getScene().getSceneTime())
|
||||
.setStopSceneTime(getScene().getSceneTime())
|
||||
.setPosOffset(getPosition().toProto())
|
||||
.setRotOffset(MathQuaternionOuterClass.MathQuaternion.newBuilder().setW(1.0F).build())
|
||||
.setMovingPlatformType(getMovingPlatformType())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,148 +1,133 @@
|
||||
package emu.grasscutter.game.managers.blossom;
|
||||
|
||||
import emu.grasscutter.data.GameData;
|
||||
import emu.grasscutter.data.excels.monster.MonsterData;
|
||||
import emu.grasscutter.data.excels.world.WorldLevelData;
|
||||
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.ChallengeTrigger;
|
||||
import emu.grasscutter.game.dungeons.challenge.trigger.KillMonsterTrigger;
|
||||
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 class BlossomActivity {
|
||||
|
||||
private static final int BLOOMING_GADGET_ID = 70210109;
|
||||
private final SceneGroup tempSceneGroup;
|
||||
private final WorldChallenge challenge;
|
||||
private final EntityGadget gadget;
|
||||
private final int goal;
|
||||
private final int worldLevel;
|
||||
private final List<EntityMonster> activeMonsters = new ArrayList<>();
|
||||
private final Queue<Integer> candidateMonsters = new ArrayDeque<>();
|
||||
private EntityGadget chest;
|
||||
private int step;
|
||||
private int generatedCount;
|
||||
private boolean pass = false;
|
||||
|
||||
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 KillMonsterTrigger());
|
||||
// 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++;
|
||||
|
||||
WorldLevelData 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++) {
|
||||
MonsterData 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,396 +1,418 @@
|
||||
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;
|
||||
|
||||
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;
|
||||
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();
|
||||
}
|
||||
|
||||
public boolean getEnergyUsage() {
|
||||
return this.energyUsage;
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ public class StaminaManager extends BasePlayerManager {
|
||||
// Returns new stamina and sends PlayerPropNotify or VehicleStaminaNotify
|
||||
public int setStamina(GameSession session, String reason, int newStamina, boolean isCharacterStamina) {
|
||||
// Target Player
|
||||
if (!GAME_OPTIONS.staminaUsage || session.getPlayer().getUnlimitedStamina()) {
|
||||
if (!GAME_OPTIONS.staminaUsage || session.getPlayer().isUnlimitedStamina()) {
|
||||
newStamina = getMaxCharacterStamina();
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ import emu.grasscutter.game.props.PlayerProperty;
|
||||
import emu.grasscutter.game.props.WatcherTriggerType;
|
||||
import emu.grasscutter.game.quest.QuestManager;
|
||||
import emu.grasscutter.game.quest.enums.QuestContent;
|
||||
import emu.grasscutter.game.quest.enums.QuestTrigger;
|
||||
import emu.grasscutter.game.shop.ShopLimit;
|
||||
import emu.grasscutter.game.tower.TowerData;
|
||||
import emu.grasscutter.game.tower.TowerManager;
|
||||
@@ -117,12 +116,13 @@ public class Player {
|
||||
@Getter @Setter private int sceneId;
|
||||
@Getter @Setter private int regionId;
|
||||
@Getter private int mainCharacterId;
|
||||
@Setter private boolean godmode; // Getter is inGodmode
|
||||
private boolean stamina; // Getter is getUnlimitedStamina, Setter is setUnlimitedStamina
|
||||
@Getter @Setter private boolean inGodMode;
|
||||
@Getter @Setter private boolean unlimitedStamina;
|
||||
|
||||
@Getter private Set<Integer> nameCardList;
|
||||
@Getter private Set<Integer> flyCloakList;
|
||||
@Getter private Set<Integer> costumeList;
|
||||
@Getter private Set<Integer> personalLineList;
|
||||
@Getter @Setter private Set<Integer> rewardedLevels;
|
||||
@Getter @Setter private Set<Integer> homeRewardedLevels;
|
||||
@Getter @Setter private Set<Integer> realmList;
|
||||
@@ -793,18 +793,6 @@ public class Player {
|
||||
this.save();
|
||||
}
|
||||
|
||||
public boolean getUnlimitedStamina() {
|
||||
return stamina;
|
||||
}
|
||||
|
||||
public void setUnlimitedStamina(boolean stamina) {
|
||||
this.stamina = stamina;
|
||||
}
|
||||
|
||||
public boolean inGodmode() {
|
||||
return godmode;
|
||||
}
|
||||
|
||||
public boolean hasSentLoginPackets() {
|
||||
return hasSentLoginPackets;
|
||||
}
|
||||
|
||||
@@ -1,238 +1,280 @@
|
||||
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.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 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));
|
||||
}
|
||||
|
||||
/**********
|
||||
* 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()
|
||||
.triggerEvent(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));
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,129 +1,72 @@
|
||||
package emu.grasscutter.game.props;
|
||||
|
||||
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 {
|
||||
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",
|
||||
2),
|
||||
Water(
|
||||
2,
|
||||
FightProperty.FIGHT_PROP_CUR_WATER_ENERGY,
|
||||
FightProperty.FIGHT_PROP_MAX_WATER_ENERGY,
|
||||
10201,
|
||||
"TeamResonance_Water_Lv2",
|
||||
3),
|
||||
Grass(
|
||||
3,
|
||||
FightProperty.FIGHT_PROP_CUR_GRASS_ENERGY,
|
||||
FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY,
|
||||
10501,
|
||||
"TeamResonance_Grass_Lv2",
|
||||
8),
|
||||
Electric(
|
||||
4,
|
||||
FightProperty.FIGHT_PROP_CUR_ELEC_ENERGY,
|
||||
FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY,
|
||||
10401,
|
||||
"TeamResonance_Electric_Lv2",
|
||||
7),
|
||||
Ice(
|
||||
5,
|
||||
FightProperty.FIGHT_PROP_CUR_ICE_ENERGY,
|
||||
FightProperty.FIGHT_PROP_MAX_ICE_ENERGY,
|
||||
10601,
|
||||
"TeamResonance_Ice_Lv2",
|
||||
5),
|
||||
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",
|
||||
4),
|
||||
Rock(
|
||||
8,
|
||||
FightProperty.FIGHT_PROP_CUR_ROCK_ENERGY,
|
||||
FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY,
|
||||
10701,
|
||||
"TeamResonance_Rock_Lv2",
|
||||
6),
|
||||
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 {
|
||||
Stream.of(values())
|
||||
.forEach(
|
||||
e -> {
|
||||
map.put(e.getValue(), e);
|
||||
stringMap.put(e.name(), e);
|
||||
});
|
||||
}
|
||||
|
||||
@Getter private final int value;
|
||||
@Getter private final int teamResonanceId;
|
||||
@Getter private final FightProperty curEnergyProp;
|
||||
@Getter private final FightProperty maxEnergyProp;
|
||||
@Getter private final int depotValue;
|
||||
@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 depotValue) {
|
||||
this.value = value;
|
||||
this.curEnergyProp = curEnergyProp;
|
||||
this.maxEnergyProp = maxEnergyProp;
|
||||
this.teamResonanceId = teamResonanceId;
|
||||
this.depotValue = depotValue;
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package emu.grasscutter.game.quest;
|
||||
|
||||
import emu.grasscutter.game.quest.enums.QuestTrigger;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface QuestValue {
|
||||
QuestTrigger value();
|
||||
}
|
||||
@@ -1,22 +1,23 @@
|
||||
package emu.grasscutter.game.quest.conditions;
|
||||
|
||||
import emu.grasscutter.data.excels.QuestData;
|
||||
import emu.grasscutter.game.player.Player;
|
||||
import emu.grasscutter.game.quest.QuestValueCond;
|
||||
import emu.grasscutter.game.quest.enums.QuestCond;
|
||||
import lombok.val;
|
||||
|
||||
@QuestValueCond(QuestCond.QUEST_COND_PERSONAL_LINE_UNLOCK)
|
||||
public class ConditionPersonalLineUnlock extends BaseCondition {
|
||||
|
||||
@Override
|
||||
public boolean execute(
|
||||
Player owner,
|
||||
QuestData questData,
|
||||
QuestData.QuestAcceptCondition condition,
|
||||
String paramStr,
|
||||
int... params) {
|
||||
val personalLineId = condition.getParam()[0];
|
||||
return owner.getPersonalLineList().contains(personalLineId);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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().refillEntityAvatarEnergy();
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +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;
|
||||
import lombok.val;
|
||||
|
||||
@QuestValueExec(QuestExec.QUEST_EXEC_SET_OPEN_STATE)
|
||||
public class ExecSetOpenState extends QuestExecHandler {
|
||||
@Override
|
||||
public boolean execute(GameQuest quest, QuestData.QuestExecParam condition, String... paramStr) {
|
||||
val 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
@@ -1,85 +1,86 @@
|
||||
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 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 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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user