From f5703e5964000bd9e73e608a8bbedde7711865db Mon Sep 17 00:00:00 2001 From: longfruit <147137915+longfruit@users.noreply.github.com> Date: Thu, 19 Oct 2023 06:18:12 -0700 Subject: [PATCH] Fix mirror tower stages; fix tower time challenge and star scoring (#2406) --- .../data/excels/tower/TowerLevelData.java | 69 ++++++++++++++--- .../game/dungeons/DungeonManager.java | 22 +++++- .../game/dungeons/DungeonSystem.java | 5 +- .../dungeons/TowerDungeonSettleListener.java | 8 +- .../dungeons/challenge/WorldChallenge.java | 9 ++- .../challenge/trigger/InTimeTrigger.java | 19 +++++ .../dungeons/dungeon_results/TowerResult.java | 17 +++-- .../game/tower/TowerLevelRecord.java | 4 + .../grasscutter/game/tower/TowerManager.java | 76 ++++++++++++++++++- .../emu/grasscutter/game/world/Scene.java | 7 ++ .../scripts/SceneScriptManager.java | 8 +- .../emu/grasscutter/scripts/ScriptLib.java | 25 +++++- .../send/PacketTowerLevelStarCondNotify.java | 48 +++++++----- 13 files changed, 264 insertions(+), 53 deletions(-) diff --git a/src/main/java/emu/grasscutter/data/excels/tower/TowerLevelData.java b/src/main/java/emu/grasscutter/data/excels/tower/TowerLevelData.java index 244b27f0b..b4486cca7 100644 --- a/src/main/java/emu/grasscutter/data/excels/tower/TowerLevelData.java +++ b/src/main/java/emu/grasscutter/data/excels/tower/TowerLevelData.java @@ -1,33 +1,82 @@ package emu.grasscutter.data.excels.tower; import emu.grasscutter.data.*; +import java.util.List; +import lombok.*; @ResourceType(name = "TowerLevelExcelConfigData.json") +@Getter public class TowerLevelData extends GameResource { private int levelId; private int levelIndex; private int levelGroupId; private int dungeonId; + private List conds; + + public static class TowerLevelCond { + private TowerCondType towerCondType; + private List argumentList; + } + + public enum TowerCondType { + TOWER_COND_NONE, + TOWER_COND_CHALLENGE_LEFT_TIME_MORE_THAN, + TOWER_COND_LEFT_HP_GREATER_THAN + } + + // Not actual data in TowerLevelExcelConfigData. + // Just packaging condition parameters for convenience. + @Getter + public class TowerCondTimeParams { + private int param1; + private int minimumTimeInSeconds; + + public TowerCondTimeParams(int param1, int minimumTimeInSeconds) { + this.param1 = param1; + this.minimumTimeInSeconds = minimumTimeInSeconds; + } + } + + @Getter + public class TowerCondHpParams { + private int sceneId; + private int configId; + private int minimumHpPercentage; + + public TowerCondHpParams(int sceneId, int configId, int minimumHpPercentage) { + this.sceneId = sceneId; + this.configId = configId; + this.minimumHpPercentage = minimumHpPercentage; + } + } @Override public int getId() { return this.getLevelId(); } - public int getLevelId() { - return levelId; + public TowerCondType getCondType(int star) { + if (star < 0 || conds == null || star >= conds.size()) { + return TowerCondType.TOWER_COND_NONE; + } + var condType = conds.get(star).towerCondType; + return condType == null ? TowerCondType.TOWER_COND_NONE : condType; } - public int getLevelGroupId() { - return levelGroupId; + public TowerCondTimeParams getTimeCond(int star) { + if (star < 0 || conds == null || star >= conds.size()) { + return null; + } + var params = conds.get(star).argumentList; + return new TowerCondTimeParams(params.get(0), params.get(1)); } - public int getLevelIndex() { - return levelIndex; - } - - public int getDungeonId() { - return dungeonId; + public TowerCondHpParams getHpCond(int star) { + if (star < 0 || conds == null || star >= conds.size()) { + return null; + } + var params = conds.get(star).argumentList; + return new TowerCondHpParams(params.get(0), params.get(1), params.get(2)); } } diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java index 5fb55a759..da8ba4dea 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonManager.java @@ -38,6 +38,7 @@ public final class DungeonManager { private boolean ended = false; private int newestWayPoint = 0; @Getter private int startSceneTime = 0; + @Setter @Getter private boolean towerDungeon = false; DungeonTrialTeam trialTeam = null; @@ -323,15 +324,30 @@ public final class DungeonManager { p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON); } }); - scene - .getScriptManager() - .callEvent(new ScriptArgs(0, EventType.EVENT_DUNGEON_SETTLE, successfully ? 1 : 0)); + var future = scene + .getScriptManager() + .callEvent(new ScriptArgs(0, EventType.EVENT_DUNGEON_SETTLE, successfully ? 1 : 0)); + // Note: There is a possible race condition with calling + // EVENT_DUNGEON_SETTLE here asynchronously: + // 1. EVENT_DUNGEON_SETTLE triggers some Lua-side logic, + // which may happen after 2 (below) finishes. + // 2. Some DungeonSettleListener could be comparing some + // Lua variable before its setting in 1 (above) finishes. + // For safety, ensure all events have finished before returning. + try { + future.get(); + } catch (Exception e) { + e.printStackTrace(); + } } public void endDungeon(BaseDungeonResult.DungeonEndReason endReason) { if (scene.getDungeonSettleListeners() != null) { scene.getDungeonSettleListeners().forEach(o -> o.onDungeonSettle(this, endReason)); } + if (isTowerDungeon()) { + scene.getPlayers().get(0).getTowerManager().onEnd(); + } ended = true; } diff --git a/src/main/java/emu/grasscutter/game/dungeons/DungeonSystem.java b/src/main/java/emu/grasscutter/game/dungeons/DungeonSystem.java index b80c2468d..49fca7c50 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/DungeonSystem.java +++ b/src/main/java/emu/grasscutter/game/dungeons/DungeonSystem.java @@ -132,7 +132,9 @@ public final class DungeonSystem extends BaseGameSystem { if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) { var scene = player.getScene(); - scene.setDungeonManager(new DungeonManager(scene, data)); + var dungeonManager = new DungeonManager(scene, data); + dungeonManager.setTowerDungeon(true); + scene.setDungeonManager(dungeonManager); dungeonSettleListeners.forEach(scene::addDungeonSettleObserver); } return true; @@ -168,6 +170,7 @@ public final class DungeonSystem extends BaseGameSystem { // clean temp team if it has player.getTeamManager().cleanTemporaryTeam(); player.getTowerManager().clearEntry(); + dungeonManager.setTowerDungeon(false); // Transfer player back to world player.getWorld().transferPlayerToScene(player, prevScene, prevPos); diff --git a/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java index ba8af7d9b..a2f826f48 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java +++ b/src/main/java/emu/grasscutter/game/dungeons/TowerDungeonSettleListener.java @@ -9,6 +9,7 @@ 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( @@ -22,17 +23,18 @@ public class TowerDungeonSettleListener implements DungeonSettleListener { } var towerManager = scene.getPlayers().get(0).getTowerManager(); + var stars = towerManager.getCurLevelStars(); - towerManager.notifyCurLevelRecordChangeWhenDone(3); + towerManager.notifyCurLevelRecordChangeWhenDone(stars); scene.broadcastPacket( new PacketTowerFloorRecordChangeNotify( - towerManager.getCurrentFloorId(), 3, towerManager.canEnterScheduleFloor())); + towerManager.getCurrentFloorId(), stars, towerManager.canEnterScheduleFloor())); var challenge = scene.getChallenge(); var dungeonStats = new DungeonEndStats( scene.getKilledMonsterCount(), challenge.getFinishedTime(), 0, endReason); - var result = new TowerResult(dungeonData, dungeonStats, towerManager, challenge); + var result = new TowerResult(dungeonData, dungeonStats, towerManager, challenge, stars); scene.broadcastPacket(new PacketDungeonSettleNotify(result)); } diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java index b755566e6..dcebee3a5 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/WorldChallenge.java @@ -80,9 +80,16 @@ public class WorldChallenge { return; } this.progress = true; - this.startedAt = System.currentTimeMillis(); + this.startedAt = getScene().getSceneTimeSeconds(); getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this)); challengeTriggers.forEach(t -> t.onBegin(this)); + + var player = scene.getPlayers().get(0); + var dungeonManager = scene.getDungeonManager(); + var towerManager = player.getTowerManager(); + if (dungeonManager != null && dungeonManager.isTowerDungeon() && towerManager != null) { + towerManager.onBegin(); + } } public void done() { diff --git a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/InTimeTrigger.java b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/InTimeTrigger.java index 99b74fabc..7842a5d80 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/InTimeTrigger.java +++ b/src/main/java/emu/grasscutter/game/dungeons/challenge/trigger/InTimeTrigger.java @@ -1,10 +1,29 @@ package emu.grasscutter.game.dungeons.challenge.trigger; import emu.grasscutter.game.dungeons.challenge.WorldChallenge; +import emu.grasscutter.server.packet.send.PacketChallengeDataNotify; public class InTimeTrigger extends ChallengeTrigger { + @Override + public void onBegin(WorldChallenge challenge) { + // Show time remaining UI + var scene = challenge.getScene(); + scene.broadcastPacket( + new PacketChallengeDataNotify( + challenge, + 2, + // Compensate for time passed so far in scene. + challenge.getTimeLimit() + scene.getSceneTimeSeconds())); + } + @Override public void onCheckTimeout(WorldChallenge challenge) { + // In Tower challenges, time can run out without + // causing the challenge to fail. (Player just + // gets 0 stars when they ultimately finish.) + var dungeonManager = challenge.getScene().getDungeonManager(); + if (dungeonManager != null && dungeonManager.isTowerDungeon()) return; + var current = challenge.getScene().getSceneTimeSeconds(); if (current - challenge.getStartedAt() > challenge.getTimeLimit()) { challenge.fail(); diff --git a/src/main/java/emu/grasscutter/game/dungeons/dungeon_results/TowerResult.java b/src/main/java/emu/grasscutter/game/dungeons/dungeon_results/TowerResult.java index 2e742efca..2b9956561 100644 --- a/src/main/java/emu/grasscutter/game/dungeons/dungeon_results/TowerResult.java +++ b/src/main/java/emu/grasscutter/game/dungeons/dungeon_results/TowerResult.java @@ -13,17 +13,20 @@ public class TowerResult extends BaseDungeonResult { boolean canJump; boolean hasNextLevel; int nextFloorId; + int currentStars; public TowerResult( DungeonData dungeonData, DungeonEndStats dungeonStats, TowerManager towerManager, - WorldChallenge challenge) { + WorldChallenge challenge, + int currentStars) { super(dungeonData, dungeonStats); this.challenge = challenge; this.canJump = towerManager.hasNextFloor(); this.hasNextLevel = towerManager.hasNextLevel(); this.nextFloorId = hasNextLevel ? 0 : towerManager.getNextFloorId(); + this.currentStars = currentStars; } @Override @@ -40,14 +43,16 @@ public class TowerResult extends BaseDungeonResult { TowerLevelEndNotify.newBuilder() .setIsSuccess(challenge.isSuccess()) .setContinueState(continueStatus) - .addFinishedStarCondList(1) - .addFinishedStarCondList(2) - .addFinishedStarCondList(3) .addRewardItemList( - ItemParamOuterClass.ItemParam.newBuilder().setItemId(201).setCount(1000).build()); + ItemParamOuterClass.ItemParam.newBuilder().setItemId(201).setCount(1000)); + + for (int i = 1; i <= currentStars; i++) { + towerLevelEndNotify.addFinishedStarCondList(i); + } + if (nextFloorId > 0 && canJump) { towerLevelEndNotify.setNextFloorId(nextFloorId); } - builder.setTowerLevelEndNotify(towerLevelEndNotify); + builder.setTowerLevelEndNotify(towerLevelEndNotify.build()); } } diff --git a/src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java b/src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java index 8c0dd94a1..245ec327d 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerLevelRecord.java @@ -25,6 +25,10 @@ public class TowerLevelRecord { return this; } + public int getLevelStars(int levelId) { + return passedLevelMap.get(levelId); + } + public int getStarCount() { return passedLevelMap.values().stream().mapToInt(Integer::intValue).sum(); } diff --git a/src/main/java/emu/grasscutter/game/tower/TowerManager.java b/src/main/java/emu/grasscutter/game/tower/TowerManager.java index f1b4bbc19..6863c7de3 100644 --- a/src/main/java/emu/grasscutter/game/tower/TowerManager.java +++ b/src/main/java/emu/grasscutter/game/tower/TowerManager.java @@ -1,16 +1,22 @@ package emu.grasscutter.game.tower; +import emu.grasscutter.Grasscutter; import emu.grasscutter.data.GameData; import emu.grasscutter.data.excels.tower.TowerLevelData; import emu.grasscutter.game.dungeons.*; import emu.grasscutter.game.player.*; import emu.grasscutter.server.packet.send.*; import java.util.*; +import lombok.*; public class TowerManager extends BasePlayerManager { private static final List towerDungeonSettleListener = List.of(new TowerDungeonSettleListener()); + private int currentPossibleStars = 0; + @Getter private boolean inProgress; + @Getter private int currentTimeLimit; + public TowerManager(Player player) { super(player); } @@ -32,6 +38,30 @@ public class TowerManager extends BasePlayerManager { return this.getTowerData().currentLevel + 1; } + public void onTick() { + var challenge = player.getScene().getChallenge(); + if (challenge == null || !challenge.inProgress()) return; + + // Check star conditions and notify client if any failed. + int stars = getCurLevelStars(); + while (stars < currentPossibleStars) { + player + .getSession() + .send(new PacketTowerLevelStarCondNotify(getTowerData().currentFloorId, getCurrentLevel(), currentPossibleStars)); + currentPossibleStars--; + } + } + + public void onBegin() { + var challenge = player.getScene().getChallenge(); + inProgress = true; + currentTimeLimit = challenge.getTimeLimit(); + } + + public void onEnd() { + inProgress = false; + } + public Map getRecordMap() { Map recordMap = getTowerData().recordMap; if (recordMap == null || recordMap.size() == 0) { @@ -84,9 +114,10 @@ public class TowerManager extends BasePlayerManager { // stop using skill player.getSession().send(new PacketCanUseSkillNotify(false)); // notify the cond of stars + currentPossibleStars = 3; player .getSession() - .send(new PacketTowerLevelStarCondNotify(getTowerData().currentFloorId, getCurrentLevel())); + .send(new PacketTowerLevelStarCondNotify(getTowerData().currentFloorId, getCurrentLevel(), currentPossibleStars + 1)); } public void notifyCurLevelRecordChange() { @@ -97,6 +128,36 @@ public class TowerManager extends BasePlayerManager { getTowerData().currentFloorId, getCurrentLevel())); } + public int getCurLevelStars() { + var scene = player.getScene(); + var challenge = scene.getChallenge(); + if (challenge == null) { + Grasscutter.getLogger().error("getCurLevelStars: no challenge registered!"); + return 0; + } + + var levelData = GameData.getTowerLevelDataMap().get(getCurrentLevelId()); + // 0-based indexing. "star" = 0 means checking for 1-star conditions. + int star; + for (star = 2; star >= 0; star--) { + var cond = levelData.getCondType(star); + if (cond == TowerLevelData.TowerCondType.TOWER_COND_CHALLENGE_LEFT_TIME_MORE_THAN) { + var params = levelData.getTimeCond(star); + var timeRemaining = challenge.getTimeLimit() - (scene.getSceneTimeSeconds() - challenge.getStartedAt()); + if (timeRemaining >= params.getMinimumTimeInSeconds()) { + break; + } + } else if (cond == TowerLevelData.TowerCondType.TOWER_COND_LEFT_HP_GREATER_THAN) { + // TODO: Check monolith health + break; + } else { + Grasscutter.getLogger().error("getCurLevelStars: Tower level {} has no or unknown condition defined for {} stars", getCurrentLevelId(), star + 1); + continue; + } + } + return star + 1; + } + public void notifyCurLevelRecordChangeWhenDone(int stars) { Map recordMap = this.getRecordMap(); int currentFloorId = getTowerData().currentFloorId; @@ -105,8 +166,17 @@ public class TowerManager extends BasePlayerManager { currentFloorId, new TowerLevelRecord(currentFloorId).setLevelStars(getCurrentLevelId(), stars)); } else { - recordMap.put( - currentFloorId, recordMap.get(currentFloorId).setLevelStars(getCurrentLevelId(), stars)); + // Only update record if better than previous + var prevRecord = recordMap.get(currentFloorId); + var passedLevelMap = prevRecord.getPassedLevelMap(); + int prevStars = 0; + if (passedLevelMap.containsKey(getCurrentLevelId())) { + prevStars = prevRecord.getLevelStars(getCurrentLevelId()); + } + if (stars > prevStars) { + recordMap.put( + currentFloorId, prevRecord.setLevelStars(getCurrentLevelId(), stars)); + } } this.getTowerData().currentLevel++; diff --git a/src/main/java/emu/grasscutter/game/world/Scene.java b/src/main/java/emu/grasscutter/game/world/Scene.java index efcaa14ea..a6b9bfebc 100644 --- a/src/main/java/emu/grasscutter/game/world/Scene.java +++ b/src/main/java/emu/grasscutter/game/world/Scene.java @@ -597,6 +597,13 @@ public class Scene { blossomManager.onTick(); + // Should be OK to check only player 0, + // as no other players could enter Tower + var towerManager = getPlayers().get(0).getTowerManager(); + if (towerManager != null) { + towerManager.onTick(); + } + this.checkNpcGroup(); this.finishLoading(); diff --git a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java index 2363e577b..6d6882546 100644 --- a/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java +++ b/src/main/java/emu/grasscutter/scripts/SceneScriptManager.java @@ -807,11 +807,11 @@ public class SceneScriptManager { } } // Events - public void callEvent(int groupId, int eventType) { - callEvent(new ScriptArgs(groupId, eventType)); + public Future callEvent(int groupId, int eventType) { + return callEvent(new ScriptArgs(groupId, eventType)); } - public void callEvent(@Nonnull ScriptArgs params) { + public Future callEvent(@Nonnull ScriptArgs params) { /** * We use ThreadLocal to trans SceneScriptManager context to ScriptLib, to avoid eval script for * every groups' trigger in every scene instances. But when callEvent is called in a ScriptLib @@ -819,7 +819,7 @@ public class SceneScriptManager { * not get it. e.g. CallEvent -> set -> ScriptLib.xxx -> CallEvent -> set -> remove -> NPE -> * (remove) So we use thread pool to clean the stack to avoid this new issue. */ - eventExecutor.submit(() -> this.realCallEvent(params)); + return eventExecutor.submit(() -> this.realCallEvent(params)); } private void realCallEvent(@Nonnull ScriptArgs params) { diff --git a/src/main/java/emu/grasscutter/scripts/ScriptLib.java b/src/main/java/emu/grasscutter/scripts/ScriptLib.java index dc1b574ff..8ebff5a65 100644 --- a/src/main/java/emu/grasscutter/scripts/ScriptLib.java +++ b/src/main/java/emu/grasscutter/scripts/ScriptLib.java @@ -190,8 +190,27 @@ public class ScriptLib { // TODO: ActivateGroupLinkBundle // TODO: ActivateGroupLinkBundleByBundleId - public int ActiveChallenge(int challengeId, int challengeIndex, int timeLimitOrGroupId, int groupId, int objectiveKills, int param5) { + public synchronized int ActiveChallenge(int challengeId, int challengeIndex, int timeLimitOrGroupId, int groupId, int objectiveKills, int param5) { logger.debug("[LUA] Call ActiveChallenge with {},{},{},{},{},{}", challengeId, challengeIndex, timeLimitOrGroupId, groupId, objectiveKills, param5); + + var scene = getSceneScriptManager().getScene(); + var existingChallenge = scene.getChallenge(); + if (existingChallenge != null && existingChallenge.inProgress()) { + logger.warn("ActiveChallenge: tried to create challenge while one is already in progress"); + return 0; + } + + var towerManager = scene.getPlayers().get(0).getTowerManager(); + if (towerManager.isInProgress()) { + // Tower scripts call ActiveChallenge twice in mirror stages. + // The second call provides the time _taken_ in the first stage, + // not the actual time limit for the challenge. + timeLimitOrGroupId = towerManager.getCurrentTimeLimit() - timeLimitOrGroupId; + if (timeLimitOrGroupId < 0) { + timeLimitOrGroupId = 0; + } + } + var challenge = ChallengeFactory.getChallenge( challengeId, challengeIndex, @@ -199,7 +218,7 @@ public class ScriptLib { groupId, objectiveKills, param5, - getSceneScriptManager().getScene(), + scene, getCurrentGroup().get() ); @@ -212,7 +231,7 @@ public class ScriptLib { dungeonChallenge.setStage(getSceneScriptManager().getVariables(groupId).getOrDefault("stage", -1) == 0); } - getSceneScriptManager().getScene().setChallenge(challenge); + scene.setChallenge(challenge); challenge.start(); return 0; } diff --git a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java index 56dcfefdf..c2c5b9036 100644 --- a/src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java +++ b/src/main/java/emu/grasscutter/server/packet/send/PacketTowerLevelStarCondNotify.java @@ -6,27 +6,37 @@ import emu.grasscutter.net.proto.TowerLevelStarCondNotifyOuterClass.TowerLevelSt public class PacketTowerLevelStarCondNotify extends BasePacket { - public PacketTowerLevelStarCondNotify(int floorId, int levelIndex) { + public PacketTowerLevelStarCondNotify(int floorId, int levelIndex, int lostStar) { super(PacketOpcodes.TowerLevelStarCondNotify); - TowerLevelStarCondNotify proto = - TowerLevelStarCondNotify.newBuilder() - .setFloorId(floorId) - .setLevelIndex(levelIndex) - .addCondDataList( - TowerLevelStarCondData.newBuilder() - // .setCondValue(1) - .build()) - .addCondDataList( - TowerLevelStarCondData.newBuilder() - // .setCondValue(2) - .build()) - .addCondDataList( - TowerLevelStarCondData.newBuilder() - // .setCondValue(3) - .build()) - .build(); + var proto = TowerLevelStarCondNotify.newBuilder() + .setFloorId(floorId) + .setLevelIndex(levelIndex); - this.setData(proto); + if (1 <= lostStar && lostStar <= 3) { + proto.addCondDataList( + TowerLevelStarCondData.newBuilder() + // If these are still obfuscated in the next client version, + // just set all int fields to the star (1 <= star <= 3) + // that failed and set all boolean fields to true. + .setNGHNFHCLFBH(lostStar) + .setIBGHBFANCBK(true) + .setOILLLBMMABH(true) + .setOMOECEGOALC(lostStar) + .build()); + } else { + proto + .addCondDataList( + TowerLevelStarCondData.newBuilder() + .build()) + .addCondDataList( + TowerLevelStarCondData.newBuilder() + .build()) + .addCondDataList( + TowerLevelStarCondData.newBuilder() + .build()); + } + + this.setData(proto.build()); } }