Reimplement gacha (#32)

* Fix some grammar warning

* Reimplement gacha

* Update GachaResult#toSpinResp declaration

---------

Co-authored-by: Yostarcc <lubenwei7758258@gmail.com>
This commit is contained in:
YostarccandYostarcc authored and GitHub committed 2026-05-05 21:49:21 -07:00
1 parent c285c878f1
commit 1316654c15
32 files changed
+1992 -450

No files matched your search

@@ -1,5 +1,6 @@
package emu.nebula.server.handlers;
import emu.nebula.Nebula;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.Public.UI32;
@@ -14,8 +15,8 @@ public class HandlerGachaGuaranteeRewardReceiveReq extends NetHandler {
// Parse req
var req = UI32.parseFrom(message);
// Recieve guaranteed reward
var change = session.getPlayer().getGachaManager().recvGuarantee(req.getValue());
// Receive guaranteed reward
var change = Nebula.getGameContext().getGachaModule().recvGuarantee(session.getPlayer(), req.getValue());
if (change == null) {
return session.encodeMsg(NetMsgId.gacha_guarantee_reward_receive_failed_ack);
@@ -1,32 +1,48 @@
package emu.nebula.server.handlers;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.net.GameSession;
import emu.nebula.net.HandlerId;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.GachaHistoriesOuterClass.GachaHistories;
import emu.nebula.proto.Public.UI32;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.gacha_histories_req)
public class HandlerGachaHistoriesReq extends NetHandler {
private static final int MAX_HISTORY_SIZE = 2000;
// Return histories from the recent 6 months only
private static final long HISTORY_RETENTION_SECONDS = 60L * 60L * 24L * 30L * 6L;
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = UI32.parseFrom(message);
// Get history log
var list = session.getPlayer().getGachaManager().getHistories().get(req.getValue());
// Build response
var rsp = GachaHistories.newInstance();
if (list != null) {
for (var log : list) {
rsp.addList(log.toProto());
}
int requestedStorageId = req.getValue();
if (requestedStorageId <= 0 || GameData.getGachaStorageDataTable().get(requestedStorageId) == null) {
return session.encodeMsg(NetMsgId.gacha_histories_failed_ack);
}
long retentionStartTime = Nebula.getCurrentServerTime() - HISTORY_RETENTION_SECONDS;
var rsp = GachaHistories.newInstance();
var logs = Nebula.getGameDatabase().getSortedObjects(
emu.nebula.game.gacha.GachaHistoryLog.class,
"playerUid",
session.getPlayer().getUid(),
"type",
requestedStorageId,
"time",
retentionStartTime,
"time",
MAX_HISTORY_SIZE
);
java.util.Collections.reverse(logs);
for (var log : logs) {
rsp.addList(log.toProto());
}
// Encode and send
return session.encodeMsg(NetMsgId.gacha_histories_succeed_ack, rsp);
}
@@ -1,10 +1,13 @@
package emu.nebula.server.handlers;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.game.gacha.GachaBannerInfo;
import emu.nebula.net.GameSession;
import emu.nebula.net.HandlerId;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.GachaInformation.GachaInformationResp;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.gacha_information_req)
public class HandlerGachaInformationReq extends NetHandler {
@@ -13,9 +16,21 @@ public class HandlerGachaInformationReq extends NetHandler {
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Build response
var rsp = GachaInformationResp.newInstance();
for (var bannerInfo : session.getPlayer().getGachaManager().getBannerInfos()) {
rsp.addInformation(bannerInfo.toProto());
var manager = session.getPlayer().getGachaManager();
synchronized (manager) {
for (var data : GameData.getGachaDataTable().values()) {
if (!data.isActiveAt(Nebula.getCurrentServerTime())) {
continue;
}
var bannerInfo = manager.findBannerInfo(data.getId());
if (bannerInfo == null) {
bannerInfo = new GachaBannerInfo(data);
}
var pityState = manager.findPityState(data.getStorageId());
rsp.addInformation(bannerInfo.toProto(pityState));
}
}
// Encode and send
@@ -1,11 +1,11 @@
package emu.nebula.server.handlers;
import emu.nebula.Nebula;
import emu.nebula.net.GameSession;
import emu.nebula.net.HandlerId;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.GachaNewbieInfoOuterClass.GachaNewbieInfo;
import emu.nebula.proto.GachaNewbieInfoOuterClass.GachaNewbieInfoResp;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.gacha_newbie_info_req)
public class HandlerGachaNewbieInfoReq extends NetHandler {
@@ -13,13 +13,9 @@ public class HandlerGachaNewbieInfoReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
var rsp = GachaNewbieInfoResp.newInstance();
var info = GachaNewbieInfo.newInstance()
.setId(5)
.setReceive(true);
rsp.addList(info);
for (var info : Nebula.getGameContext().getGachaModule().listNewbieInfos(session.getPlayer())) {
rsp.addList(info);
}
return session.encodeMsg(NetMsgId.gacha_newbie_info_succeed_ack, rsp);
}
@@ -1,21 +1,28 @@
package emu.nebula.server.handlers;
import emu.nebula.Nebula;
import emu.nebula.net.GameSession;
import emu.nebula.net.HandlerId;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.GachaNewbieObtain.GachaNewbieObtainReq;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
@HandlerId(NetMsgId.gacha_newbie_obtain_req)
public class HandlerGachaNewbieObtainReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
@SuppressWarnings("unused")
var req = GachaNewbieObtainReq.parseFrom(message);
// TODO
return session.encodeMsg(NetMsgId.gacha_newbie_obtain_failed_ack);
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());
}
}
@@ -0,0 +1,25 @@
package emu.nebula.server.handlers;
import emu.nebula.Nebula;
import emu.nebula.net.GameSession;
import emu.nebula.net.HandlerId;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.proto.GachaNewbieSave.GachaNewbieSaveReq;
@HandlerId(NetMsgId.gacha_newbie_save_req)
public class HandlerGachaNewbieSaveReq extends NetHandler {
@Override
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) {
return session.encodeMsg(NetMsgId.gacha_newbie_save_failed_ack);
}
return session.encodeMsg(NetMsgId.gacha_newbie_save_succeed_ack);
}
}
@@ -1,16 +1,33 @@
package emu.nebula.server.handlers;
import emu.nebula.Nebula;
import emu.nebula.game.gacha.GachaMode;
import emu.nebula.net.GameSession;
import emu.nebula.net.HandlerId;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.net.HandlerId;
import emu.nebula.net.GameSession;
import emu.nebula.proto.GachaNewbieSpin.GachaNewbieSpinResp;
import emu.nebula.proto.GachaSpin.GachaSpinReq;
@HandlerId(NetMsgId.gacha_newbie_spin_req)
public class HandlerGachaNewbieSpinReq extends NetHandler {
@Override
public byte[] handle(GameSession session, byte[] message) throws Exception {
return session.encodeMsg(NetMsgId.gacha_newbie_spin_failed_ack);
var req = GachaSpinReq.parseFrom(message);
Integer amount = GachaMode.getAmountByMode(req.getMode());
if (amount == null) {
return session.encodeMsg(NetMsgId.gacha_newbie_spin_failed_ack);
}
int[] cards = Nebula.getGameContext().getGachaModule().spinNewbie(session.getPlayer(), req.getId());
if (cards == null) {
return session.encodeMsg(NetMsgId.gacha_newbie_spin_failed_ack);
}
var rsp = GachaNewbieSpinResp.newInstance();
rsp.addAllCards(cards);
return session.encodeMsg(NetMsgId.gacha_newbie_spin_succeed_ack, rsp);
}
}
@@ -1,14 +1,13 @@
package emu.nebula.server.handlers;
import emu.nebula.Nebula;
import emu.nebula.data.GameData;
import emu.nebula.game.gacha.GachaMode;
import emu.nebula.net.GameSession;
import emu.nebula.net.HandlerId;
import emu.nebula.net.NetHandler;
import emu.nebula.net.NetMsgId;
import emu.nebula.net.HandlerId;
import emu.nebula.Nebula;
import emu.nebula.net.GameSession;
import emu.nebula.proto.GachaSpin.GachaCard;
import emu.nebula.proto.GachaSpin.GachaSpinReq;
import emu.nebula.proto.GachaSpin.GachaSpinResp;
import emu.nebula.proto.Public.ItemTpl;
@HandlerId(NetMsgId.gacha_spin_req)
public class HandlerGachaSpinReq extends NetHandler {
@@ -17,34 +16,24 @@ public class HandlerGachaSpinReq extends NetHandler {
public byte[] handle(GameSession session, byte[] message) throws Exception {
// Parse request
var req = GachaSpinReq.parseFrom(message);
Integer amount = GachaMode.getAmountByMode(req.getMode());
if (amount == null) {
return session.encodeMsg(NetMsgId.gacha_spin_failed_ack);
}
// Do gacha
var result = Nebula.getGameContext().getGachaModule().spin(
session.getPlayer(),
req.getId(),
req.getMode()
amount
);
if (result == null) {
return session.encodeMsg(NetMsgId.gacha_spin_failed_ack);
}
// Build response
var rsp = GachaSpinResp.newInstance()
.setTime(Nebula.getCurrentServerTime())
.setAMissTimes(result.getInfo().getMissTimesA())
.setAupMissTimes(result.getInfo().getMissTimesA())
.setTotalTimes(result.getInfo().getTotal())
.setGachaTotalTimes(result.getInfo().getTotal())
.setAupGuaranteeTimes(result.getInfo().isUsedGuarantee() ? 0 : 1)
.setChange(result.getChange().toProto());
for (int id : result.getCards()) {
var card = GachaCard.newInstance()
.setCard(ItemTpl.newInstance().setTid(id).setQty(1));
rsp.addCards(card);
}
var gachaData = GameData.getGachaDataTable().get(req.getId());
var rsp = result.toSpinResp(gachaData);
// Encode and send response
return session.encodeMsg(NetMsgId.gacha_spin_succeed_ack, rsp);