Update GaCy-GO
This commit is contained in:
1 parent
da8a32f60d
commit
0cbe460e49
345 files changed
+98202
-77559
No files matched your search
@@ -0,0 +1,302 @@
|
||||
package gameserver
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"gac-hkrpg-go/internal/common"
|
||||
pb "gac-hkrpg-go/protocol/proto"
|
||||
)
|
||||
|
||||
func init() {
|
||||
handle("GetGachaInfo", func(s *Session, req *pb.GetGachaInfoCsReq, rsp *pb.GetGachaInfoScRsp) error {
|
||||
banners, _, err := common.LoadBrowsableGachaBanners()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(banners) == 0 {
|
||||
rsp.Retcode = uint32(pb.Retcode_RET_GACHA_NOT_SUPPORT)
|
||||
PacketLog("GACHA entry rejected: no verified installed banner assets")
|
||||
return nil
|
||||
}
|
||||
config, err := common.LoadRuntimeConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, banner := range banners {
|
||||
info := &pb.GachaInfo{GachaId: banner.ID, BeginTime: *config.ActivitySchedule.BeginTime, EndTime: *config.ActivitySchedule.EndTime, GachaCeiling: &pb.GachaCeiling{}}
|
||||
if banner.AvatarID != 0 {
|
||||
info.PrizeItemList = []uint32{banner.AvatarID}
|
||||
info.ItemDetailList = []uint32{banner.AvatarID}
|
||||
}
|
||||
rsp.GachaInfoList = append(rsp.GachaInfoList, info)
|
||||
}
|
||||
// Synthetic IDs are not native Client config rows. The presentation
|
||||
// adapter registers its own pools; sending these IDs in GetGachaInfo has
|
||||
// caused Client response handlers to throw. Keep the preview opt-in.
|
||||
mapped, _, err := common.LoadLimitedGachaBanners()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, _, policy, err := common.LoadSandboxDrawPool()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
count := policy.MappedBannerPreviewCount
|
||||
if count > uint32(len(mapped)) {
|
||||
count = uint32(len(mapped))
|
||||
}
|
||||
for i := uint32(0); i < count; i++ {
|
||||
banner := mapped[i]
|
||||
info := &pb.GachaInfo{GachaId: banner.GachaID, BeginTime: *config.ActivitySchedule.BeginTime, EndTime: *config.ActivitySchedule.EndTime, GachaCeiling: &pb.GachaCeiling{}}
|
||||
info.PrizeItemList = []uint32{banner.AvatarID}
|
||||
info.ItemDetailList = append([]uint32{banner.AvatarID}, banner.FourStarIDs...)
|
||||
rsp.GachaInfoList = append(rsp.GachaInfoList, info)
|
||||
}
|
||||
PacketLog(fmt.Sprintf("GACHA_INFO banners=%d mapped_preview=%d", len(banners), count))
|
||||
return nil
|
||||
})
|
||||
handle("DoGacha", func(s *Session, req *pb.DoGachaCsReq, rsp *pb.DoGachaScRsp) error {
|
||||
rsp.GachaId = req.GachaId
|
||||
rsp.Retcode = uint32(pb.Retcode_RET_GACHA_ID_NOT_EXIST)
|
||||
banners, _, err := common.LoadFeaturedGachaBanners()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var avatarID uint32
|
||||
for _, row := range banners {
|
||||
if row.ID == req.GachaId {
|
||||
avatarID = row.AvatarID
|
||||
break
|
||||
}
|
||||
}
|
||||
if avatarID == 0 {
|
||||
mapped, _, err := common.LoadLimitedGachaBanners()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, row := range mapped {
|
||||
if row.GachaID == req.GachaId {
|
||||
return drawLimitedAvatar(s, req, rsp, row)
|
||||
}
|
||||
}
|
||||
}
|
||||
if avatarID != 0 {
|
||||
return drawFeaturedAvatar(s, req, rsp, avatarID)
|
||||
}
|
||||
// Featured-only path found nothing. Verified browsable banners may
|
||||
// still draw from the policy-declared sandbox pool: every pick is a
|
||||
// duplicate grant on an avatar from the live imported roster, so no
|
||||
// invented IDs or first-acquisition synthesis are involved.
|
||||
sandboxDrawn, err := drawSandboxPool(s, req, rsp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sandboxDrawn {
|
||||
return nil
|
||||
}
|
||||
rsp.Retcode = uint32(pb.Retcode_RET_GACHA_NOT_SUPPORT)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// drawFeaturedAvatar grants req.GachaNum copies of one featured avatar using
|
||||
// its configured first Eidolon cost as the duplicate conversion for captured
|
||||
// basename-derived banners. Mapped limited banners use drawLimitedAvatar.
|
||||
func drawFeaturedAvatar(s *Session, req *pb.DoGachaCsReq, rsp *pb.DoGachaScRsp, avatarID uint32) error {
|
||||
_, policy, err := common.LoadFeaturedGachaBanners()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rsp.Retcode = uint32(pb.Retcode_RET_GACHA_NUM_INVALID)
|
||||
allowed := false
|
||||
for _, n := range policy.DrawCounts {
|
||||
allowed = allowed || n == req.GachaNum
|
||||
}
|
||||
if !allowed {
|
||||
return nil
|
||||
}
|
||||
rsp.Retcode = uint32(pb.Retcode_RET_GACHA_NOT_SUPPORT)
|
||||
if _, active := CURRENT_BATTLE.Load(s.Token); active {
|
||||
return nil
|
||||
}
|
||||
player := s.JSON()
|
||||
if player == nil || player.Avatars[avatarID] == nil {
|
||||
return nil
|
||||
}
|
||||
resources, err := loadEidolonResources()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
row, exists := resources.Avatars[avatarID]
|
||||
if !exists || len(row.Ranks) == 0 {
|
||||
return nil
|
||||
}
|
||||
additions := map[uint32]uint32{}
|
||||
for _, cost := range row.Ranks[0].Cost {
|
||||
amount := uint64(cost.ItemNum)*uint64(req.GachaNum) + uint64(additions[cost.ItemID])
|
||||
if amount > uint64(^uint32(0)) {
|
||||
return nil
|
||||
}
|
||||
additions[cost.ItemID] = uint32(amount)
|
||||
}
|
||||
bag, err := bagMaterials(player)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
baseline := map[uint32]uint32{}
|
||||
for _, item := range bag {
|
||||
baseline[item.Tid] = item.Num
|
||||
}
|
||||
updated, err := common.GrantGachaDuplicate(avatarID, additions, baseline)
|
||||
if err != nil {
|
||||
PacketLog("GACHA rejected: " + err.Error())
|
||||
return nil
|
||||
}
|
||||
if player.Materials == nil {
|
||||
player.Materials = map[uint32]uint32{}
|
||||
}
|
||||
sync := &pb.PlayerSyncScNotify{}
|
||||
for id, count := range updated {
|
||||
player.Materials[id] = count
|
||||
sync.MaterialList = append(sync.MaterialList, &pb.Material{Tid: id, Num: count})
|
||||
}
|
||||
for i := uint32(0); i < req.GachaNum; i++ {
|
||||
transfer := &pb.ItemList{}
|
||||
for _, cost := range row.Ranks[0].Cost {
|
||||
transfer.ItemList = append(transfer.ItemList, &pb.Item{ItemId: cost.ItemID, Num: cost.ItemNum})
|
||||
}
|
||||
rsp.GachaItemList = append(rsp.GachaItemList, &pb.GachaItem{GachaItem: &pb.Item{ItemId: avatarID, Num: 1}, TokenItem: &pb.ItemList{}, TransferItemList: transfer})
|
||||
}
|
||||
if err = s.Send(sync); err != nil {
|
||||
return err
|
||||
}
|
||||
rsp.Retcode = 0
|
||||
rsp.GachaNum = req.GachaNum
|
||||
PacketLog(fmt.Sprintf("GACHA banner=%d avatar=%d draws=%d sandbox=featured", req.GachaId, avatarID, req.GachaNum))
|
||||
return nil
|
||||
}
|
||||
|
||||
// drawSandboxPool serves a draw from the policy-declared sandbox pool for an
|
||||
// asset-verified browsable banner. Every pick is a duplicate-material grant on
|
||||
// an avatar from the live imported roster; nothing is synthesized. Returns
|
||||
// false when the request is not a verified banner or the pool is disabled, so
|
||||
// the caller keeps its original rejection.
|
||||
func drawSandboxPool(s *Session, req *pb.DoGachaCsReq, rsp *pb.DoGachaScRsp) (bool, error) {
|
||||
verified, _, err := common.LoadBrowsableGachaBanners()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
isVerified := false
|
||||
for _, row := range verified {
|
||||
isVerified = isVerified || row.ID == req.GachaId
|
||||
}
|
||||
if !isVerified {
|
||||
return false, nil
|
||||
}
|
||||
enabled, pool, policy, err := common.LoadSandboxDrawPool()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !enabled || len(pool) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
rsp.Retcode = uint32(pb.Retcode_RET_GACHA_NUM_INVALID)
|
||||
allowed := false
|
||||
for _, n := range policy.DrawCounts {
|
||||
allowed = allowed || n == req.GachaNum
|
||||
}
|
||||
if !allowed {
|
||||
return true, nil
|
||||
}
|
||||
rsp.Retcode = uint32(pb.Retcode_RET_GACHA_NOT_SUPPORT)
|
||||
if _, active := CURRENT_BATTLE.Load(s.Token); active {
|
||||
return true, nil
|
||||
}
|
||||
resources, err := loadEidolonResources()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Uniform sandbox picks. Avatars without a configured first-rank Eidolon
|
||||
// cost are excluded: their duplicate grant has no declared conversion.
|
||||
type rosterAvatar struct {
|
||||
id uint32
|
||||
itemID, itemNum uint32
|
||||
}
|
||||
effective := make([]rosterAvatar, 0, len(pool))
|
||||
for _, id := range pool {
|
||||
row, exists := resources.Avatars[id]
|
||||
if !exists || len(row.Ranks) == 0 || len(row.Ranks[0].Cost) == 0 {
|
||||
continue
|
||||
}
|
||||
effective = append(effective, rosterAvatar{id: id, itemID: row.Ranks[0].Cost[0].ItemID, itemNum: row.Ranks[0].Cost[0].ItemNum})
|
||||
}
|
||||
if len(effective) == 0 {
|
||||
PacketLog("GACHA sandbox pool empty: no roster avatar with configured Eidolon cost")
|
||||
return true, nil
|
||||
}
|
||||
player := s.JSON()
|
||||
if player == nil {
|
||||
return true, nil
|
||||
}
|
||||
picks := make([]rosterAvatar, 0, req.GachaNum)
|
||||
for i := uint32(0); i < req.GachaNum; i++ {
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(effective))))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
picks = append(picks, effective[n.Int64()])
|
||||
}
|
||||
counts := map[uint32]uint32{}
|
||||
costOf := map[uint32]rosterAvatar{}
|
||||
for _, pick := range picks {
|
||||
counts[pick.id]++
|
||||
costOf[pick.id] = pick
|
||||
}
|
||||
bag, err := bagMaterials(player)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
baseline := map[uint32]uint32{}
|
||||
for _, item := range bag {
|
||||
baseline[item.Tid] = item.Num
|
||||
}
|
||||
updated := map[uint32]uint32{}
|
||||
for id, times := range counts {
|
||||
cost := costOf[id]
|
||||
total := uint64(cost.itemNum) * uint64(times)
|
||||
if total > uint64(^uint32(0)) {
|
||||
return true, nil
|
||||
}
|
||||
additions := map[uint32]uint32{cost.itemID: uint32(total)}
|
||||
got, err := common.GrantGachaDuplicate(id, additions, baseline)
|
||||
if err != nil {
|
||||
PacketLog("GACHA sandbox rejected: " + err.Error())
|
||||
return true, nil
|
||||
}
|
||||
for itemID, count := range got {
|
||||
updated[itemID] = count
|
||||
}
|
||||
}
|
||||
if player.Materials == nil {
|
||||
player.Materials = map[uint32]uint32{}
|
||||
}
|
||||
sync := &pb.PlayerSyncScNotify{}
|
||||
for id, count := range updated {
|
||||
player.Materials[id] = count
|
||||
sync.MaterialList = append(sync.MaterialList, &pb.Material{Tid: id, Num: count})
|
||||
}
|
||||
for _, pick := range picks {
|
||||
transfer := &pb.ItemList{}
|
||||
transfer.ItemList = append(transfer.ItemList, &pb.Item{ItemId: pick.itemID, Num: pick.itemNum})
|
||||
rsp.GachaItemList = append(rsp.GachaItemList, &pb.GachaItem{GachaItem: &pb.Item{ItemId: pick.id, Num: 1}, TokenItem: &pb.ItemList{}, TransferItemList: transfer})
|
||||
}
|
||||
if err = s.Send(sync); err != nil {
|
||||
return false, err
|
||||
}
|
||||
rsp.Retcode = 0
|
||||
rsp.GachaNum = req.GachaNum
|
||||
PacketLog(fmt.Sprintf("GACHA banner=%d draws=%d sandbox=imported_roster pool=%d distinct=%d", req.GachaId, req.GachaNum, len(effective), len(counts)))
|
||||
return true, nil
|
||||
}
|
||||
Reference in new issue
Block a user