Fix mirror tower stages; fix tower time challenge and star scoring (#2406)

This commit is contained in:
longfruit 2023-10-19 06:18:12 -07:00 committed by GitHub
parent bc8e7c21ce
commit f5703e5964
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 264 additions and 53 deletions

View File

@ -1,33 +1,82 @@
package emu.grasscutter.data.excels.tower; package emu.grasscutter.data.excels.tower;
import emu.grasscutter.data.*; import emu.grasscutter.data.*;
import java.util.List;
import lombok.*;
@ResourceType(name = "TowerLevelExcelConfigData.json") @ResourceType(name = "TowerLevelExcelConfigData.json")
@Getter
public class TowerLevelData extends GameResource { public class TowerLevelData extends GameResource {
private int levelId; private int levelId;
private int levelIndex; private int levelIndex;
private int levelGroupId; private int levelGroupId;
private int dungeonId; private int dungeonId;
private List<TowerLevelCond> conds;
public static class TowerLevelCond {
private TowerCondType towerCondType;
private List<Integer> 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 @Override
public int getId() { public int getId() {
return this.getLevelId(); return this.getLevelId();
} }
public int getLevelId() { public TowerCondType getCondType(int star) {
return levelId; 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() { public TowerCondTimeParams getTimeCond(int star) {
return levelGroupId; 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() { public TowerCondHpParams getHpCond(int star) {
return levelIndex; if (star < 0 || conds == null || star >= conds.size()) {
return null;
} }
var params = conds.get(star).argumentList;
public int getDungeonId() { return new TowerCondHpParams(params.get(0), params.get(1), params.get(2));
return dungeonId;
} }
} }

View File

@ -38,6 +38,7 @@ public final class DungeonManager {
private boolean ended = false; private boolean ended = false;
private int newestWayPoint = 0; private int newestWayPoint = 0;
@Getter private int startSceneTime = 0; @Getter private int startSceneTime = 0;
@Setter @Getter private boolean towerDungeon = false;
DungeonTrialTeam trialTeam = null; DungeonTrialTeam trialTeam = null;
@ -323,15 +324,30 @@ public final class DungeonManager {
p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON); p.getBattlePassManager().triggerMission(WatcherTriggerType.TRIGGER_FINISH_DUNGEON);
} }
}); });
scene var future = scene
.getScriptManager() .getScriptManager()
.callEvent(new ScriptArgs(0, EventType.EVENT_DUNGEON_SETTLE, successfully ? 1 : 0)); .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) { public void endDungeon(BaseDungeonResult.DungeonEndReason endReason) {
if (scene.getDungeonSettleListeners() != null) { if (scene.getDungeonSettleListeners() != null) {
scene.getDungeonSettleListeners().forEach(o -> o.onDungeonSettle(this, endReason)); scene.getDungeonSettleListeners().forEach(o -> o.onDungeonSettle(this, endReason));
} }
if (isTowerDungeon()) {
scene.getPlayers().get(0).getTowerManager().onEnd();
}
ended = true; ended = true;
} }

View File

@ -132,7 +132,9 @@ public final class DungeonSystem extends BaseGameSystem {
if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) { if (player.getWorld().transferPlayerToScene(player, data.getSceneId(), data)) {
var scene = player.getScene(); 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); dungeonSettleListeners.forEach(scene::addDungeonSettleObserver);
} }
return true; return true;
@ -168,6 +170,7 @@ public final class DungeonSystem extends BaseGameSystem {
// clean temp team if it has // clean temp team if it has
player.getTeamManager().cleanTemporaryTeam(); player.getTeamManager().cleanTemporaryTeam();
player.getTowerManager().clearEntry(); player.getTowerManager().clearEntry();
dungeonManager.setTowerDungeon(false);
// Transfer player back to world // Transfer player back to world
player.getWorld().transferPlayerToScene(player, prevScene, prevPos); player.getWorld().transferPlayerToScene(player, prevScene, prevPos);

View File

@ -9,6 +9,7 @@ public class TowerDungeonSettleListener implements DungeonSettleListener {
@Override @Override
public void onDungeonSettle(DungeonManager dungeonManager, DungeonEndReason endReason) { public void onDungeonSettle(DungeonManager dungeonManager, DungeonEndReason endReason) {
var scene = dungeonManager.getScene(); var scene = dungeonManager.getScene();
var dungeonData = dungeonManager.getDungeonData(); var dungeonData = dungeonManager.getDungeonData();
if (scene.getLoadedGroups().stream() if (scene.getLoadedGroups().stream()
.anyMatch( .anyMatch(
@ -22,17 +23,18 @@ public class TowerDungeonSettleListener implements DungeonSettleListener {
} }
var towerManager = scene.getPlayers().get(0).getTowerManager(); var towerManager = scene.getPlayers().get(0).getTowerManager();
var stars = towerManager.getCurLevelStars();
towerManager.notifyCurLevelRecordChangeWhenDone(3); towerManager.notifyCurLevelRecordChangeWhenDone(stars);
scene.broadcastPacket( scene.broadcastPacket(
new PacketTowerFloorRecordChangeNotify( new PacketTowerFloorRecordChangeNotify(
towerManager.getCurrentFloorId(), 3, towerManager.canEnterScheduleFloor())); towerManager.getCurrentFloorId(), stars, towerManager.canEnterScheduleFloor()));
var challenge = scene.getChallenge(); var challenge = scene.getChallenge();
var dungeonStats = var dungeonStats =
new DungeonEndStats( new DungeonEndStats(
scene.getKilledMonsterCount(), challenge.getFinishedTime(), 0, endReason); 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)); scene.broadcastPacket(new PacketDungeonSettleNotify(result));
} }

View File

@ -80,9 +80,16 @@ public class WorldChallenge {
return; return;
} }
this.progress = true; this.progress = true;
this.startedAt = System.currentTimeMillis(); this.startedAt = getScene().getSceneTimeSeconds();
getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this)); getScene().broadcastPacket(new PacketDungeonChallengeBeginNotify(this));
challengeTriggers.forEach(t -> t.onBegin(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() { public void done() {

View File

@ -1,10 +1,29 @@
package emu.grasscutter.game.dungeons.challenge.trigger; package emu.grasscutter.game.dungeons.challenge.trigger;
import emu.grasscutter.game.dungeons.challenge.WorldChallenge; import emu.grasscutter.game.dungeons.challenge.WorldChallenge;
import emu.grasscutter.server.packet.send.PacketChallengeDataNotify;
public class InTimeTrigger extends ChallengeTrigger { 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 @Override
public void onCheckTimeout(WorldChallenge challenge) { 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(); var current = challenge.getScene().getSceneTimeSeconds();
if (current - challenge.getStartedAt() > challenge.getTimeLimit()) { if (current - challenge.getStartedAt() > challenge.getTimeLimit()) {
challenge.fail(); challenge.fail();

View File

@ -13,17 +13,20 @@ public class TowerResult extends BaseDungeonResult {
boolean canJump; boolean canJump;
boolean hasNextLevel; boolean hasNextLevel;
int nextFloorId; int nextFloorId;
int currentStars;
public TowerResult( public TowerResult(
DungeonData dungeonData, DungeonData dungeonData,
DungeonEndStats dungeonStats, DungeonEndStats dungeonStats,
TowerManager towerManager, TowerManager towerManager,
WorldChallenge challenge) { WorldChallenge challenge,
int currentStars) {
super(dungeonData, dungeonStats); super(dungeonData, dungeonStats);
this.challenge = challenge; this.challenge = challenge;
this.canJump = towerManager.hasNextFloor(); this.canJump = towerManager.hasNextFloor();
this.hasNextLevel = towerManager.hasNextLevel(); this.hasNextLevel = towerManager.hasNextLevel();
this.nextFloorId = hasNextLevel ? 0 : towerManager.getNextFloorId(); this.nextFloorId = hasNextLevel ? 0 : towerManager.getNextFloorId();
this.currentStars = currentStars;
} }
@Override @Override
@ -40,14 +43,16 @@ public class TowerResult extends BaseDungeonResult {
TowerLevelEndNotify.newBuilder() TowerLevelEndNotify.newBuilder()
.setIsSuccess(challenge.isSuccess()) .setIsSuccess(challenge.isSuccess())
.setContinueState(continueStatus) .setContinueState(continueStatus)
.addFinishedStarCondList(1)
.addFinishedStarCondList(2)
.addFinishedStarCondList(3)
.addRewardItemList( .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) { if (nextFloorId > 0 && canJump) {
towerLevelEndNotify.setNextFloorId(nextFloorId); towerLevelEndNotify.setNextFloorId(nextFloorId);
} }
builder.setTowerLevelEndNotify(towerLevelEndNotify); builder.setTowerLevelEndNotify(towerLevelEndNotify.build());
} }
} }

View File

@ -25,6 +25,10 @@ public class TowerLevelRecord {
return this; return this;
} }
public int getLevelStars(int levelId) {
return passedLevelMap.get(levelId);
}
public int getStarCount() { public int getStarCount() {
return passedLevelMap.values().stream().mapToInt(Integer::intValue).sum(); return passedLevelMap.values().stream().mapToInt(Integer::intValue).sum();
} }

View File

@ -1,16 +1,22 @@
package emu.grasscutter.game.tower; package emu.grasscutter.game.tower;
import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.GameData; import emu.grasscutter.data.GameData;
import emu.grasscutter.data.excels.tower.TowerLevelData; import emu.grasscutter.data.excels.tower.TowerLevelData;
import emu.grasscutter.game.dungeons.*; import emu.grasscutter.game.dungeons.*;
import emu.grasscutter.game.player.*; import emu.grasscutter.game.player.*;
import emu.grasscutter.server.packet.send.*; import emu.grasscutter.server.packet.send.*;
import java.util.*; import java.util.*;
import lombok.*;
public class TowerManager extends BasePlayerManager { public class TowerManager extends BasePlayerManager {
private static final List<DungeonSettleListener> towerDungeonSettleListener = private static final List<DungeonSettleListener> towerDungeonSettleListener =
List.of(new TowerDungeonSettleListener()); List.of(new TowerDungeonSettleListener());
private int currentPossibleStars = 0;
@Getter private boolean inProgress;
@Getter private int currentTimeLimit;
public TowerManager(Player player) { public TowerManager(Player player) {
super(player); super(player);
} }
@ -32,6 +38,30 @@ public class TowerManager extends BasePlayerManager {
return this.getTowerData().currentLevel + 1; 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<Integer, TowerLevelRecord> getRecordMap() { public Map<Integer, TowerLevelRecord> getRecordMap() {
Map<Integer, TowerLevelRecord> recordMap = getTowerData().recordMap; Map<Integer, TowerLevelRecord> recordMap = getTowerData().recordMap;
if (recordMap == null || recordMap.size() == 0) { if (recordMap == null || recordMap.size() == 0) {
@ -84,9 +114,10 @@ public class TowerManager extends BasePlayerManager {
// stop using skill // stop using skill
player.getSession().send(new PacketCanUseSkillNotify(false)); player.getSession().send(new PacketCanUseSkillNotify(false));
// notify the cond of stars // notify the cond of stars
currentPossibleStars = 3;
player player
.getSession() .getSession()
.send(new PacketTowerLevelStarCondNotify(getTowerData().currentFloorId, getCurrentLevel())); .send(new PacketTowerLevelStarCondNotify(getTowerData().currentFloorId, getCurrentLevel(), currentPossibleStars + 1));
} }
public void notifyCurLevelRecordChange() { public void notifyCurLevelRecordChange() {
@ -97,6 +128,36 @@ public class TowerManager extends BasePlayerManager {
getTowerData().currentFloorId, getCurrentLevel())); 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) { public void notifyCurLevelRecordChangeWhenDone(int stars) {
Map<Integer, TowerLevelRecord> recordMap = this.getRecordMap(); Map<Integer, TowerLevelRecord> recordMap = this.getRecordMap();
int currentFloorId = getTowerData().currentFloorId; int currentFloorId = getTowerData().currentFloorId;
@ -105,8 +166,17 @@ public class TowerManager extends BasePlayerManager {
currentFloorId, currentFloorId,
new TowerLevelRecord(currentFloorId).setLevelStars(getCurrentLevelId(), stars)); new TowerLevelRecord(currentFloorId).setLevelStars(getCurrentLevelId(), stars));
} else { } else {
// 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( recordMap.put(
currentFloorId, recordMap.get(currentFloorId).setLevelStars(getCurrentLevelId(), stars)); currentFloorId, prevRecord.setLevelStars(getCurrentLevelId(), stars));
}
} }
this.getTowerData().currentLevel++; this.getTowerData().currentLevel++;

View File

@ -597,6 +597,13 @@ public class Scene {
blossomManager.onTick(); 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.checkNpcGroup();
this.finishLoading(); this.finishLoading();

View File

@ -807,11 +807,11 @@ public class SceneScriptManager {
} }
} }
// Events // Events
public void callEvent(int groupId, int eventType) { public Future<?> callEvent(int groupId, int eventType) {
callEvent(new ScriptArgs(groupId, 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 * 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 * 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 -> * 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. * (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) { private void realCallEvent(@Nonnull ScriptArgs params) {

View File

@ -190,8 +190,27 @@ public class ScriptLib {
// TODO: ActivateGroupLinkBundle // TODO: ActivateGroupLinkBundle
// TODO: ActivateGroupLinkBundleByBundleId // 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); 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( var challenge = ChallengeFactory.getChallenge(
challengeId, challengeId,
challengeIndex, challengeIndex,
@ -199,7 +218,7 @@ public class ScriptLib {
groupId, groupId,
objectiveKills, objectiveKills,
param5, param5,
getSceneScriptManager().getScene(), scene,
getCurrentGroup().get() getCurrentGroup().get()
); );
@ -212,7 +231,7 @@ public class ScriptLib {
dungeonChallenge.setStage(getSceneScriptManager().getVariables(groupId).getOrDefault("stage", -1) == 0); dungeonChallenge.setStage(getSceneScriptManager().getVariables(groupId).getOrDefault("stage", -1) == 0);
} }
getSceneScriptManager().getScene().setChallenge(challenge); scene.setChallenge(challenge);
challenge.start(); challenge.start();
return 0; return 0;
} }

View File

@ -6,27 +6,37 @@ import emu.grasscutter.net.proto.TowerLevelStarCondNotifyOuterClass.TowerLevelSt
public class PacketTowerLevelStarCondNotify extends BasePacket { public class PacketTowerLevelStarCondNotify extends BasePacket {
public PacketTowerLevelStarCondNotify(int floorId, int levelIndex) { public PacketTowerLevelStarCondNotify(int floorId, int levelIndex, int lostStar) {
super(PacketOpcodes.TowerLevelStarCondNotify); super(PacketOpcodes.TowerLevelStarCondNotify);
TowerLevelStarCondNotify proto = var proto = TowerLevelStarCondNotify.newBuilder()
TowerLevelStarCondNotify.newBuilder()
.setFloorId(floorId) .setFloorId(floorId)
.setLevelIndex(levelIndex) .setLevelIndex(levelIndex);
.addCondDataList(
TowerLevelStarCondData.newBuilder()
// .setCondValue(1)
.build())
.addCondDataList(
TowerLevelStarCondData.newBuilder()
// .setCondValue(2)
.build())
.addCondDataList(
TowerLevelStarCondData.newBuilder()
// .setCondValue(3)
.build())
.build();
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());
} }
} }