Fix cant claim beginner banner results and simplify newbie gacha flow

This commit is contained in:
Yostarcc
2026-05-10 21:38:47 -07:00
committed by Melledy
parent 276c8b2ebd
commit 580a7a92e7
6 changed files with 70 additions and 139 deletions
@@ -45,20 +45,20 @@ public class GachaManager extends PlayerManager implements GameDatabaseObject {
this.save(); this.save();
} }
public synchronized NewbieObtainLock lockNewbieObtain(int newbieId) { public synchronized boolean tryLockNewbieObtain(int newbieId) {
if (this.lockedNewbieObtainIds.contains(newbieId)) { if (this.lockedNewbieObtainIds.contains(newbieId)) {
return null; return false;
} }
this.lockedNewbieObtainIds.add(newbieId); this.lockedNewbieObtainIds.add(newbieId);
return new NewbieObtainLockHandle(newbieId); return true;
} }
public synchronized boolean isNewbieObtainLocked(int newbieId) { public synchronized boolean isNewbieObtainLocked(int newbieId) {
return this.lockedNewbieObtainIds.contains(newbieId); return this.lockedNewbieObtainIds.contains(newbieId);
} }
private synchronized void unlockNewbieObtainInternal(int newbieId) { public synchronized void unlockNewbieObtain(int newbieId) {
this.lockedNewbieObtainIds.remove(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();
}
} }
@@ -18,10 +18,12 @@ import it.unimi.dsi.fastutil.ints.IntArrayList;
import java.util.List; import java.util.List;
public class GachaModule extends GameContextModule { public class GachaModule extends GameContextModule {
private final NewbieGachaModule newbieGachaModule = new NewbieGachaModule();
private final NewbieGachaModule newbieGachaModule;
public GachaModule(GameContext context) { public GachaModule(GameContext context) {
super(context); super(context);
this.newbieGachaModule = new NewbieGachaModule();
} }
public GachaResult spin(Player player, int bannerId, int amount) { public GachaResult spin(Player player, int bannerId, int amount) {
@@ -14,9 +14,6 @@ import java.util.List;
public final class NewbieGachaModule { public final class NewbieGachaModule {
private record NewbieRequest(GachaManager manager, GachaNewbieDef newbieDef) {
}
public List<GachaNewbieInfo> listInfos(Player player) { public List<GachaNewbieInfo> listInfos(Player player) {
var newbieDefs = GameData.getGachaNewbieDataTable().values(); var newbieDefs = GameData.getGachaNewbieDataTable().values();
var infos = new ArrayList<GachaNewbieInfo>(newbieDefs.size()); var infos = new ArrayList<GachaNewbieInfo>(newbieDefs.size());
@@ -54,21 +51,20 @@ public final class NewbieGachaModule {
} }
public int[] spin(Player player, int newbieId) { public int[] spin(Player player, int newbieId) {
var request = resolveRequest(player, newbieId); GachaNewbieDef newbieDef = GameData.getGachaNewbieDataTable().get(newbieId);
if (request == null) { if (newbieDef == null) {
return null; return null;
} }
int newbieStateId = request.newbieDef().getId(); int newbieStateId = newbieDef.getId();
var bannerDef = GameData.getGachaDataTable().get(newbieStateId); var bannerDef = GameData.getGachaDataTable().get(newbieStateId);
if (bannerDef == null) { if (bannerDef == null) {
return null; return null;
} }
var manager = request.manager(); synchronized (player.getGachaManager()) {
synchronized (manager) { NewbieGachaState state = player.getGachaManager().getOrCreateNewbieState(newbieDef);
var state = loadStateForSpin(manager, request.newbieDef()); if (player.getGachaManager().isNewbieObtainLocked(newbieStateId) || !state.canSpin()) {
if (state == null) {
return null; return null;
} }
@@ -81,56 +77,52 @@ public final class NewbieGachaModule {
return null; return null;
} }
manager.saveNewbieState(state); player.getGachaManager().saveNewbieState(state);
return cards; return cards;
} }
} }
public boolean save(Player player, int newbieId, Integer index) { public boolean save(Player player, int newbieId, Integer index) {
int resolvedIndex = index == null ? -1 : index; var newbieDef = GameData.getGachaNewbieDataTable().get(newbieId);
var request = resolveRequest(player, newbieId); if (newbieDef == null) {
if (request == null) {
return false; return false;
} }
var manager = request.manager(); synchronized (player.getGachaManager()) {
synchronized (manager) { if (player.getGachaManager().isNewbieObtainLocked(newbieId)) {
var state = loadStateForSave(manager, request.newbieDef()); return false;
if (state == null) { }
var state = player.getGachaManager().getOrCreateNewbieState(newbieDef);
if (state == null || !state.canSavePendingResult()) {
return false; return false;
} }
if (!state.savePendingResult(resolvedIndex)) { if (!state.savePendingResult(index)) {
return false; return false;
} }
manager.saveNewbieState(state); player.getGachaManager().saveNewbieState(state);
return true; return true;
} }
} }
public PlayerChangeInfo obtain(Player player, int newbieId, int index) { public PlayerChangeInfo obtain(Player player, int newbieId, int index) {
if (index < 0) { GachaNewbieDef gachaNewbieDef = GameData.getGachaNewbieDataTable().get(newbieId);
if (gachaNewbieDef == null) {
return null; return null;
} }
var request = resolveRequest(player, newbieId); int newbieStateId = gachaNewbieDef.getId();
if (request == null) { if (!player.getGachaManager().tryLockNewbieObtain(newbieStateId)) {
return null; return null;
} }
int newbieStateId = request.newbieDef().getId(); try {
var obtainLock = request.manager().lockNewbieObtain(newbieStateId);
if (obtainLock == null) {
return null;
}
try (var ignored = obtainLock) {
NewbieGachaState state; NewbieGachaState state;
int[] cards; int[] cards;
synchronized (request.manager()) { synchronized (player.getGachaManager()) {
state = loadStateForObtain(request.manager(), request.newbieDef(), index); state = player.getGachaManager().findNewbieState(newbieStateId);
if (state == null) { if (state == null || !state.canObtain(index)) {
return null; return null;
} }
@@ -147,66 +139,19 @@ public final class NewbieGachaModule {
return null; return null;
} }
synchronized (request.manager()) { synchronized (player.getGachaManager()) {
if (!state.markReceived(index)) { if (!state.markReceived(index)) {
return null; return null;
} }
request.manager().saveNewbieState(state); player.getGachaManager().saveNewbieState(state);
} }
GachaRewardResolver.apply(player, rewardPlan, change); GachaRewardResolver.apply(player, rewardPlan, change);
return 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;
}
} }
@@ -30,25 +30,23 @@ public class NewbieGachaState {
} }
// Checks if the player can perform a spin. // Checks if the player can perform a spin.
public boolean canSpin(boolean obtainLocked) { public boolean canSpin() {
return !received && !obtainLocked && remainingSpinCount > 0; return !received && remainingSpinCount > 0;
} }
// Checks if the current pending result can be moved to saved results // Checks if the current pending result can be moved to saved results
public boolean canSavePendingResult(boolean obtainLocked) { public boolean canSavePendingResult() {
return !received && !obtainLocked && hasPendingResult(); return !received && hasPendingResult();
} }
// Updates the maximum allowed saved results. // Updates the maximum allowed saved results.
public boolean applyConfig(int saveCount) { public void applyConfig(int saveCount) {
int oldSaveCount = this.saveCount;
this.saveCount = Math.max(1, 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 // Applies a new spin result to the pending slot and consumes a spin attempt
public boolean applySpinResult(int[] cards) { public boolean applySpinResult(int[] cards) {
if (!canSpin(false) || cards == null || cards.length == 0) { if (!canSpin() || cards == null || cards.length == 0) {
return false; 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 // Saves the pending result into the saved results list at the specified index or adds it
public boolean savePendingResult(int index) { public boolean savePendingResult(Integer index) {
if (!hasPendingResult() || received) return false; if (!hasPendingResult() || this.received) {
return false;
}
if (index >= 0 && index < savedResults.size()) { if (index != null && index < savedResults.size()) {
// Replace existing slot // Replace existing slot
savedResults.set(index, pendingResult); savedResults.set(index, pendingResult);
} else if (savedResults.size() < saveCount) { } else if (index == null && savedResults.size() < saveCount) {
// Add new slot if capacity allows // Add new slot if capacity allows
savedResults.add(pendingResult); savedResults.add(pendingResult);
} else { } else {
@@ -75,19 +75,26 @@ public class NewbieGachaState {
return true; 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) { public int[] copySavedResult(int index) {
return (index >= 0 && index < savedResults.size()) ? savedResults.get(index).clone() : null; if (index == 0) {
return hasPendingResult() ? pendingResult.clone() : null;
} }
// Check for claiming a specific result int savedResultIndex = index - 1;
return (savedResultIndex >= 0 && savedResultIndex < savedResults.size()) ? savedResults.get(savedResultIndex).clone() : null;
}
// 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) { 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) { public boolean markReceived(int index) {
if (received || index < 0 || index >= savedResults.size()) { if (received || copySavedResult(index) == null) {
return false; return false;
} }
@@ -13,15 +13,15 @@ public class HandlerGachaNewbieObtainReq extends NetHandler {
@Override @Override
public byte[] handle(GameSession session, byte[] message) throws Exception { public byte[] handle(GameSession session, byte[] message) throws Exception {
var req = GachaNewbieObtainReq.parseFrom(message); 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()); var change = Nebula.getGameContext().getGachaModule().obtainNewbie(session.getPlayer(), req.getId(), req.getIdx());
if (change == null) { if (change == null) {
return session.encodeMsg(NetMsgId.gacha_newbie_obtain_failed_ack); 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()); return session.encodeMsg(NetMsgId.gacha_newbie_obtain_succeed_ack, change.toProto());
} }
@@ -14,8 +14,11 @@ public class HandlerGachaNewbieSaveReq extends NetHandler {
public byte[] handle(GameSession session, byte[] message) throws Exception { public byte[] handle(GameSession session, byte[] message) throws Exception {
var req = GachaNewbieSaveReq.parseFrom(message); var req = GachaNewbieSaveReq.parseFrom(message);
Integer index = req.hasIdx() ? req.getIdx() : null; Integer index = req.hasIdx() ? req.getIdx() : null;
boolean succeeded = Nebula.getGameContext().getGachaModule().saveNewbie(session.getPlayer(), req.getId(), index); if (!req.hasId() || req.getId() < 0 || (index != null && index < 0)) {
if (!succeeded) { 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); return session.encodeMsg(NetMsgId.gacha_newbie_save_failed_ack);
} }