Format code

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

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult;
public interface DungeonSettleListener {
void onDungeonSettle(DungeonManager dungeonManager, BaseDungeonResult.DungeonEndReason endReason);
}
package emu.grasscutter.game.dungeons;
import emu.grasscutter.game.dungeons.dungeon_results.BaseDungeonResult;
public interface DungeonSettleListener {
void onDungeonSettle(DungeonManager dungeonManager, BaseDungeonResult.DungeonEndReason endReason);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,5 +7,14 @@ import emu.grasscutter.scripts.data.SceneGroup;
public interface ChallengeFactoryHandler {
boolean isThisType(ChallengeType challengeType);
WorldChallenge build(int challengeIndex, int challengeId, int param3, int param4, int param5, int param6, Scene scene, SceneGroup group);
WorldChallenge build(
int challengeIndex,
int challengeId,
int param3,
int param4,
int param5,
int param6,
Scene scene,
SceneGroup group);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,22 @@
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.scripts.data.SceneTrigger;
public abstract class ChallengeTrigger {
public void onBegin(WorldChallenge challenge) { }
public void onFinish(WorldChallenge challenge) { }
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) { }
public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget) { }
public void onCheckTimeout(WorldChallenge challenge) { }
public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) { }
public void onGroupTrigger(WorldChallenge challenge, SceneTrigger trigger) { }
}
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityGadget;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.scripts.data.SceneTrigger;
public abstract class ChallengeTrigger {
public void onBegin(WorldChallenge challenge) {}
public void onFinish(WorldChallenge challenge) {}
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {}
public void onGadgetDeath(WorldChallenge challenge, EntityGadget gadget) {}
public void onCheckTimeout(WorldChallenge challenge) {}
public void onGadgetDamage(WorldChallenge challenge, EntityGadget gadget) {}
public void onGroupTrigger(WorldChallenge challenge, SceneTrigger trigger) {}
}

View File

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

View File

@@ -1,22 +1,25 @@
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class KillMonsterTrigger extends ChallengeTrigger{
private int monsterCfgId;
@Override
public void onBegin(WorldChallenge challenge) {
challenge.getScene().broadcastPacket(new PacketChallengeDataNotify(challenge, 1, challenge.getScore().get()));
}
@Override
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {
if(monster.getConfigId() == monsterCfgId){
challenge.done();
}
}
}
package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.game.entity.EntityMonster;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class KillMonsterTrigger extends ChallengeTrigger {
private int monsterCfgId;
@Override
public void onBegin(WorldChallenge challenge) {
challenge
.getScene()
.broadcastPacket(new PacketChallengeDataNotify(challenge, 1, challenge.getScore().get()));
}
@Override
public void onMonsterDeath(WorldChallenge challenge, EntityMonster monster) {
if (monster.getConfigId() == monsterCfgId) {
challenge.done();
}
}
}