From 580a7a92e784a7de708df6ee4afb935846d8f14a Mon Sep 17 00:00:00 2001 From: Yostarcc Date: Sun, 10 May 2026 21:24:53 +0800 Subject: [PATCH] Fix cant claim beginner banner results and simplify newbie gacha flow --- .../emu/nebula/game/gacha/GachaManager.java | 34 +----- .../emu/nebula/game/gacha/GachaModule.java | 4 +- .../nebula/game/gacha/NewbieGachaModule.java | 113 +++++------------- .../nebula/game/gacha/NewbieGachaState.java | 43 ++++--- .../handlers/HandlerGachaNewbieObtainReq.java | 8 +- .../handlers/HandlerGachaNewbieSaveReq.java | 7 +- 6 files changed, 70 insertions(+), 139 deletions(-) diff --git a/src/main/java/emu/nebula/game/gacha/GachaManager.java b/src/main/java/emu/nebula/game/gacha/GachaManager.java index 2b2c363..d04a9c0 100644 --- a/src/main/java/emu/nebula/game/gacha/GachaManager.java +++ b/src/main/java/emu/nebula/game/gacha/GachaManager.java @@ -45,20 +45,20 @@ public class GachaManager extends PlayerManager implements GameDatabaseObject { this.save(); } - public synchronized NewbieObtainLock lockNewbieObtain(int newbieId) { + public synchronized boolean tryLockNewbieObtain(int newbieId) { if (this.lockedNewbieObtainIds.contains(newbieId)) { - return null; + return false; } this.lockedNewbieObtainIds.add(newbieId); - return new NewbieObtainLockHandle(newbieId); + return true; } public synchronized boolean isNewbieObtainLocked(int newbieId) { return this.lockedNewbieObtainIds.contains(newbieId); } - private synchronized void unlockNewbieObtainInternal(int newbieId) { + public synchronized void unlockNewbieObtain(int newbieId) { this.lockedNewbieObtainIds.remove(newbieId); } @@ -145,30 +145,4 @@ public class GachaManager extends PlayerManager implements GameDatabaseObject { ); } - private final class NewbieObtainLockHandle implements NewbieObtainLock { - private final int newbieId; - private boolean closed; - - private NewbieObtainLockHandle(int newbieId) { - this.newbieId = newbieId; - } - - @Override - public void close() { - synchronized (GachaManager.this) { - if (this.closed) { - return; - } - - this.closed = true; - unlockNewbieObtainInternal(this.newbieId); - } - } - } - - public interface NewbieObtainLock extends AutoCloseable { - @Override - void close(); - } - } diff --git a/src/main/java/emu/nebula/game/gacha/GachaModule.java b/src/main/java/emu/nebula/game/gacha/GachaModule.java index 8b7c1fa..0371821 100644 --- a/src/main/java/emu/nebula/game/gacha/GachaModule.java +++ b/src/main/java/emu/nebula/game/gacha/GachaModule.java @@ -18,10 +18,12 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; import java.util.List; public class GachaModule extends GameContextModule { - private final NewbieGachaModule newbieGachaModule = new NewbieGachaModule(); + + private final NewbieGachaModule newbieGachaModule; public GachaModule(GameContext context) { super(context); + this.newbieGachaModule = new NewbieGachaModule(); } public GachaResult spin(Player player, int bannerId, int amount) { diff --git a/src/main/java/emu/nebula/game/gacha/NewbieGachaModule.java b/src/main/java/emu/nebula/game/gacha/NewbieGachaModule.java index a53c871..2e0111d 100644 --- a/src/main/java/emu/nebula/game/gacha/NewbieGachaModule.java +++ b/src/main/java/emu/nebula/game/gacha/NewbieGachaModule.java @@ -14,9 +14,6 @@ import java.util.List; public final class NewbieGachaModule { - private record NewbieRequest(GachaManager manager, GachaNewbieDef newbieDef) { - } - public List listInfos(Player player) { var newbieDefs = GameData.getGachaNewbieDataTable().values(); var infos = new ArrayList(newbieDefs.size()); @@ -54,21 +51,20 @@ public final class NewbieGachaModule { } public int[] spin(Player player, int newbieId) { - var request = resolveRequest(player, newbieId); - if (request == null) { + GachaNewbieDef newbieDef = GameData.getGachaNewbieDataTable().get(newbieId); + if (newbieDef == null) { return null; } - int newbieStateId = request.newbieDef().getId(); + int newbieStateId = newbieDef.getId(); var bannerDef = GameData.getGachaDataTable().get(newbieStateId); if (bannerDef == null) { return null; } - var manager = request.manager(); - synchronized (manager) { - var state = loadStateForSpin(manager, request.newbieDef()); - if (state == null) { + synchronized (player.getGachaManager()) { + NewbieGachaState state = player.getGachaManager().getOrCreateNewbieState(newbieDef); + if (player.getGachaManager().isNewbieObtainLocked(newbieStateId) || !state.canSpin()) { return null; } @@ -81,56 +77,52 @@ public final class NewbieGachaModule { return null; } - manager.saveNewbieState(state); + player.getGachaManager().saveNewbieState(state); return cards; } } public boolean save(Player player, int newbieId, Integer index) { - int resolvedIndex = index == null ? -1 : index; - var request = resolveRequest(player, newbieId); - if (request == null) { + var newbieDef = GameData.getGachaNewbieDataTable().get(newbieId); + if (newbieDef == null) { return false; } - var manager = request.manager(); - synchronized (manager) { - var state = loadStateForSave(manager, request.newbieDef()); - if (state == null) { + synchronized (player.getGachaManager()) { + if (player.getGachaManager().isNewbieObtainLocked(newbieId)) { + return false; + } + var state = player.getGachaManager().getOrCreateNewbieState(newbieDef); + if (state == null || !state.canSavePendingResult()) { return false; } - if (!state.savePendingResult(resolvedIndex)) { + if (!state.savePendingResult(index)) { return false; } - manager.saveNewbieState(state); + player.getGachaManager().saveNewbieState(state); return true; } } public PlayerChangeInfo obtain(Player player, int newbieId, int index) { - if (index < 0) { + GachaNewbieDef gachaNewbieDef = GameData.getGachaNewbieDataTable().get(newbieId); + if (gachaNewbieDef == null) { return null; } - var request = resolveRequest(player, newbieId); - if (request == null) { + int newbieStateId = gachaNewbieDef.getId(); + if (!player.getGachaManager().tryLockNewbieObtain(newbieStateId)) { return null; } - int newbieStateId = request.newbieDef().getId(); - var obtainLock = request.manager().lockNewbieObtain(newbieStateId); - if (obtainLock == null) { - return null; - } - - try (var ignored = obtainLock) { + try { NewbieGachaState state; int[] cards; - synchronized (request.manager()) { - state = loadStateForObtain(request.manager(), request.newbieDef(), index); - if (state == null) { + synchronized (player.getGachaManager()) { + state = player.getGachaManager().findNewbieState(newbieStateId); + if (state == null || !state.canObtain(index)) { return null; } @@ -147,66 +139,19 @@ public final class NewbieGachaModule { return null; } - synchronized (request.manager()) { + synchronized (player.getGachaManager()) { if (!state.markReceived(index)) { return null; } - request.manager().saveNewbieState(state); + player.getGachaManager().saveNewbieState(state); } GachaRewardResolver.apply(player, rewardPlan, change); return change; + } finally { + player.getGachaManager().unlockNewbieObtain(newbieStateId); } } - private NewbieRequest resolveRequest(Player player, int newbieId) { - var newbieDef = GameData.getGachaNewbieDataTable().get(newbieId); - if (newbieDef == null) { - return null; - } - - return new NewbieRequest(player.getGachaManager(), newbieDef); - } - - private NewbieGachaState loadStateForSpin(GachaManager manager, GachaNewbieDef newbieDef) { - int newbieStateId = newbieDef.getId(); - if (manager.isNewbieObtainLocked(newbieStateId)) { - return null; - } - var state = manager.getOrCreateNewbieState(newbieDef); - if (!state.canSpin(false)) { - return null; - } - - return state; - } - - private NewbieGachaState loadStateForSave(GachaManager manager, - GachaNewbieDef newbieDef) { - int newbieStateId = newbieDef.getId(); - if (manager.isNewbieObtainLocked(newbieStateId)) { - return null; - } - var state = manager.getOrCreateNewbieState(newbieDef); - if (!state.canSavePendingResult(false)) { - return null; - } - - return state; - } - - private NewbieGachaState loadStateForObtain(GachaManager manager, GachaNewbieDef newbieDef, int index) { - int newbieStateId = newbieDef.getId(); - var state = manager.findNewbieState(newbieStateId); - if (state == null) { - return null; - } - if (!state.canObtain(index)) { - return null; - } - - return state; - } - } diff --git a/src/main/java/emu/nebula/game/gacha/NewbieGachaState.java b/src/main/java/emu/nebula/game/gacha/NewbieGachaState.java index cd0d99b..d674f5c 100644 --- a/src/main/java/emu/nebula/game/gacha/NewbieGachaState.java +++ b/src/main/java/emu/nebula/game/gacha/NewbieGachaState.java @@ -30,25 +30,23 @@ public class NewbieGachaState { } // Checks if the player can perform a spin. - public boolean canSpin(boolean obtainLocked) { - return !received && !obtainLocked && remainingSpinCount > 0; + public boolean canSpin() { + return !received && remainingSpinCount > 0; } // Checks if the current pending result can be moved to saved results - public boolean canSavePendingResult(boolean obtainLocked) { - return !received && !obtainLocked && hasPendingResult(); + public boolean canSavePendingResult() { + return !received && hasPendingResult(); } // Updates the maximum allowed saved results. - public boolean applyConfig(int saveCount) { - int oldSaveCount = this.saveCount; + public void applyConfig(int saveCount) { this.saveCount = Math.max(1, saveCount); - return this.saveCount != oldSaveCount; } // Applies a new spin result to the pending slot and consumes a spin attempt public boolean applySpinResult(int[] cards) { - if (!canSpin(false) || cards == null || cards.length == 0) { + if (!canSpin() || cards == null || cards.length == 0) { return false; } @@ -58,13 +56,15 @@ public class NewbieGachaState { } // Saves the pending result into the saved results list at the specified index or adds it - public boolean savePendingResult(int index) { - if (!hasPendingResult() || received) return false; + public boolean savePendingResult(Integer index) { + if (!hasPendingResult() || this.received) { + return false; + } - if (index >= 0 && index < savedResults.size()) { + if (index != null && index < savedResults.size()) { // Replace existing slot savedResults.set(index, pendingResult); - } else if (savedResults.size() < saveCount) { + } else if (index == null && savedResults.size() < saveCount) { // Add new slot if capacity allows savedResults.add(pendingResult); } else { @@ -75,19 +75,26 @@ public class NewbieGachaState { return true; } - // Returns a clone of the saved result at the given index + // Returns a clone of the result referenced by obtain request index. + // Idx=0 -> pendingResult, Idx=1-saveCount -> savedResults[idx-1] public int[] copySavedResult(int index) { - return (index >= 0 && index < savedResults.size()) ? savedResults.get(index).clone() : null; + if (index == 0) { + return hasPendingResult() ? pendingResult.clone() : null; + } + + int savedResultIndex = index - 1; + return (savedResultIndex >= 0 && savedResultIndex < savedResults.size()) ? savedResults.get(savedResultIndex).clone() : null; } - // Check for claiming a specific result + // Checks whether the obtain request index points to a valid result. + // Idx=0 -> pendingResult, Idx=1-saveCount -> savedResults[idx-1] public boolean canObtain(int index) { - return !received && index >= 0 && index < savedResults.size(); + return !received && copySavedResult(index) != null; } - // Marks a specific saved result as claimed and closes the gacha session + // Marks the selected obtain request index as claimed and closes the gacha session. public boolean markReceived(int index) { - if (received || index < 0 || index >= savedResults.size()) { + if (received || copySavedResult(index) == null) { return false; } diff --git a/src/main/java/emu/nebula/server/handlers/HandlerGachaNewbieObtainReq.java b/src/main/java/emu/nebula/server/handlers/HandlerGachaNewbieObtainReq.java index 526e66e..05a207c 100644 --- a/src/main/java/emu/nebula/server/handlers/HandlerGachaNewbieObtainReq.java +++ b/src/main/java/emu/nebula/server/handlers/HandlerGachaNewbieObtainReq.java @@ -13,15 +13,15 @@ public class HandlerGachaNewbieObtainReq extends NetHandler { @Override public byte[] handle(GameSession session, byte[] message) throws Exception { var req = GachaNewbieObtainReq.parseFrom(message); + if (!req.hasId() || req.getId() < 0 || (req.hasIdx() && req.getIdx() < 0)) { + return session.encodeMsg(NetMsgId.gacha_newbie_obtain_failed_ack); + } + // req.getIdx() is optional, it always has a value var change = Nebula.getGameContext().getGachaModule().obtainNewbie(session.getPlayer(), req.getId(), req.getIdx()); if (change == null) { return session.encodeMsg(NetMsgId.gacha_newbie_obtain_failed_ack); } - if (!change.isEmpty()) { - session.getPlayer().addNextPackage(NetMsgId.items_change_notify, change.toProto()); - } - return session.encodeMsg(NetMsgId.gacha_newbie_obtain_succeed_ack, change.toProto()); } diff --git a/src/main/java/emu/nebula/server/handlers/HandlerGachaNewbieSaveReq.java b/src/main/java/emu/nebula/server/handlers/HandlerGachaNewbieSaveReq.java index aa8f55b..be6c7bb 100644 --- a/src/main/java/emu/nebula/server/handlers/HandlerGachaNewbieSaveReq.java +++ b/src/main/java/emu/nebula/server/handlers/HandlerGachaNewbieSaveReq.java @@ -14,8 +14,11 @@ public class HandlerGachaNewbieSaveReq extends NetHandler { public byte[] handle(GameSession session, byte[] message) throws Exception { var req = GachaNewbieSaveReq.parseFrom(message); Integer index = req.hasIdx() ? req.getIdx() : null; - boolean succeeded = Nebula.getGameContext().getGachaModule().saveNewbie(session.getPlayer(), req.getId(), index); - if (!succeeded) { + if (!req.hasId() || req.getId() < 0 || (index != null && index < 0)) { + return session.encodeMsg(NetMsgId.gacha_newbie_save_failed_ack); + } + boolean newbieSaveResult = Nebula.getGameContext().getGachaModule().saveNewbie(session.getPlayer(), req.getId(), index); + if (!newbieSaveResult) { return session.encodeMsg(NetMsgId.gacha_newbie_save_failed_ack); }