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,382 @@
|
||||
package gameserver
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
"gac-hkrpg-go/internal/common"
|
||||
pb "gac-hkrpg-go/protocol/proto"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// The current named command table, FFGO's extracted descriptor, and the native
|
||||
// GridFightOrbServerData.GetOrb sender agree on this request and its wire tags.
|
||||
// Do not introduce a numeric alias for the reference's descriptive name.
|
||||
func init() {
|
||||
rawNamed("IKMOCJBMGOJ", func(s *Session, payload []byte) error {
|
||||
rsp := &pb.PNHDGCBLPGD{Retcode: uint32(pb.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH)}
|
||||
var req pb.IKMOCJBMGOJ
|
||||
if err := proto.Unmarshal(payload, &req); err != nil {
|
||||
return cwSend(s, rsp)
|
||||
}
|
||||
if err := cwUseOrbs(s, &req); err != nil {
|
||||
var sendError *cwPacketSendError
|
||||
if errors.As(err, &sendError) {
|
||||
return err
|
||||
}
|
||||
rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_CONF_MISS)
|
||||
PacketLog("CW_USE_ORB rejected without consuming: " + err.Error())
|
||||
return cwSend(s, rsp)
|
||||
}
|
||||
rsp.Retcode = 0
|
||||
return cwSend(s, rsp)
|
||||
})
|
||||
}
|
||||
|
||||
type cwOrbPayout struct {
|
||||
UID uint32
|
||||
Drop *pb.GridFightDropInfo
|
||||
Updates []*pb.KFNCAIHIHCM
|
||||
}
|
||||
|
||||
func cwUseOrbs(s *Session, req *pb.IKMOCJBMGOJ) error {
|
||||
c, data, err := cwPreparingData(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
run := data.DHFOFLFCLAH
|
||||
var bag *pb.DBONCGFHHDN
|
||||
for _, component := range run.NAGDJGMMCBE {
|
||||
if v := component.GetDBAGKJNPDOL(); v != nil {
|
||||
bag = v
|
||||
}
|
||||
}
|
||||
if bag == nil {
|
||||
return fmt.Errorf("CW Orb component missing")
|
||||
}
|
||||
owned := make(map[uint32]*pb.IBPDPCOGDEP, len(bag.MDKGCIKOMLI))
|
||||
for _, orb := range bag.MDKGCIKOMLI {
|
||||
if orb == nil || orb.UniqueId == 0 || owned[orb.UniqueId] != nil {
|
||||
return fmt.Errorf("CW Orb inventory identity invalid")
|
||||
}
|
||||
owned[orb.UniqueId] = orb
|
||||
}
|
||||
uids := slices.Clone(req.CBMGDBJHLHO)
|
||||
if req.IsGetAll {
|
||||
uids = nil
|
||||
for _, orb := range bag.MDKGCIKOMLI {
|
||||
uids = append(uids, orb.UniqueId)
|
||||
}
|
||||
}
|
||||
if len(uids) == 0 {
|
||||
return fmt.Errorf("no owned Orbs selected")
|
||||
}
|
||||
recipes, err := common.LoadCurrencyWarsOrbRecipes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
captured, err := common.LoadCurrencyWarsRewards(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stage, _ := cwStage(run)
|
||||
if stage == nil {
|
||||
return fmt.Errorf("CW stage missing")
|
||||
}
|
||||
used := map[uint32]bool{}
|
||||
payouts := make([]cwOrbPayout, 0, len(uids))
|
||||
for _, uid := range uids {
|
||||
orb := owned[uid]
|
||||
if orb == nil || used[uid] {
|
||||
return fmt.Errorf("Orb UID %d missing or duplicated", uid)
|
||||
}
|
||||
used[uid] = true
|
||||
row, ok := captured.Orbs[orb.LDBNOMPCOKK]
|
||||
if !ok {
|
||||
return fmt.Errorf("Orb %d not present in captured Client table", orb.LDBNOMPCOKK)
|
||||
}
|
||||
match := false
|
||||
for _, reference := range recipes.Orbs {
|
||||
if reference.ID == row.ID && reference.Bonus == row.Bonus {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
return fmt.Errorf("Orb %d reference bonus differs from captured Client table", row.ID)
|
||||
}
|
||||
choices, err := recipes.Choices(row.ID, stage.DCAHJELNJNP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Reject the whole recipe before rolling if a branch cannot execute. This
|
||||
// avoids silently conditioning the reward distribution on supported kinds.
|
||||
for _, choice := range choices {
|
||||
if len(choice) == 0 {
|
||||
return fmt.Errorf("Orb has an empty reward choice")
|
||||
}
|
||||
for _, reward := range choice {
|
||||
if !cwOrbRewardSupported(reward.Reward) {
|
||||
return fmt.Errorf("Orb %d reward %s needs its effect implementation", row.ID, reward.Reward)
|
||||
}
|
||||
}
|
||||
}
|
||||
pick, err := rand.Int(rand.Reader, big.NewInt(int64(len(choices))))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payout := cwOrbPayout{UID: uid, Drop: &pb.GridFightDropInfo{}}
|
||||
for _, reward := range choices[pick.Int64()] {
|
||||
updates, drops, err := cwApplyOrbReward(c, run, recipes, reward)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payout.Updates = append(payout.Updates, updates...)
|
||||
payout.Drop.NMGIABHIJJB = append(payout.Drop.NMGIABHIJJB, drops...)
|
||||
}
|
||||
payout.Updates = append(payout.Updates, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_GOHHJLAPMBB{GOHHJLAPMBB: uid}})
|
||||
payouts = append(payouts, payout)
|
||||
}
|
||||
bag.MDKGCIKOMLI = slices.DeleteFunc(bag.MDKGCIKOMLI, func(v *pb.IBPDPCOGDEP) bool { return used[v.UniqueId] })
|
||||
// The detached snapshot plus CAS commits inventory and all rewards together.
|
||||
// Neither failed validation nor a stale external revision consumes an Orb.
|
||||
if err = s.saveCurrencyWarsData(data, c); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, payout := range payouts {
|
||||
// Native FFGO sends the drop presentation before the grouped sync. Only
|
||||
// scalar drop fields 1/2/3 plus the exact role-star oneof (tag101).
|
||||
if err = cwSend(s, &pb.GDHFMHKFEAL{FKNNKCKLFMD: payout.UID, NMGIABHIJJB: payout.Drop}); err != nil {
|
||||
return err
|
||||
}
|
||||
// Native OrbSummarizer requires source1 and first context uint=OrbUID.
|
||||
if err = cwSend(s, &pb.GridFightSyncUpdateResultScNotify{GCPNIHHBGGD: []*pb.ABPLALCHNFA{{GridUpdateSrc: pb.JJDFGMMMCMI_JJDFGMMMCMI_DBIEKOMKBIN, OOEOCGLLHFO: []uint32{payout.UID}, JKPJFKCBIEM: payout.Updates}}}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
PacketLog(fmt.Sprintf("CW_USE_ORB saved count=%d revision=%d", len(payouts), s.currencyWarsRevision))
|
||||
return nil
|
||||
}
|
||||
|
||||
func cwOrbRewardSupported(kind string) bool {
|
||||
switch kind {
|
||||
case "Gold", "Exp", "Refresh", "GlobalHp", "Item", "RandomEquip", "RandomRole":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cwApplyOrbReward(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, recipes *common.CurrencyWarsOrbRecipes, reward common.CurrencyWarsOrbReward) ([]*pb.KFNCAIHIHCM, []*pb.BCCONKJHDJL, error) {
|
||||
player, _, shop := cwComponents(run)
|
||||
if player == nil || shop == nil {
|
||||
return nil, nil, fmt.Errorf("CW Orb player/shop components missing")
|
||||
}
|
||||
var updates []*pb.KFNCAIHIHCM
|
||||
drop := &pb.BCCONKJHDJL{}
|
||||
add := func(current, delta uint32) (uint32, error) {
|
||||
if uint64(current)+uint64(delta) > uint64(^uint32(0)) {
|
||||
return 0, fmt.Errorf("CW Orb reward overflow")
|
||||
}
|
||||
return current + delta, nil
|
||||
}
|
||||
var err error
|
||||
switch reward.Reward {
|
||||
case "Gold":
|
||||
if reward.Amount == 0 {
|
||||
return nil, nil, fmt.Errorf("CW Orb gold amount is missing")
|
||||
}
|
||||
player.Gold, err = add(player.Gold, reward.Amount)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
updates = append(updates, cwGoldSync(player.Gold))
|
||||
drop.FKKGLFOHGGA = pb.MJJIFGINJAJ_MJJIFGINJAJ_item_value
|
||||
drop.Num = reward.Amount
|
||||
case "Exp":
|
||||
if reward.Amount == 0 {
|
||||
return nil, nil, fmt.Errorf("CW Orb EXP amount is missing")
|
||||
}
|
||||
if err = cwApplyBaseExperience(c, player, shop, reward.Amount); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
updates = append(updates, cwCapacitySync(player), cwShopSync(shop), &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PLPHEIBLPDL{PLPHEIBLPDL: &pb.PIHKLPJGEPM{Level: player.EHNJDIDABHD, Exp: player.GridFightCurLevelExp, HFMKLAPEKFA: player.NDAINFLOLPJ}}})
|
||||
drop.FKKGLFOHGGA = pb.MJJIFGINJAJ_MJJIFGINJAJ_exp
|
||||
drop.Num = reward.Amount
|
||||
case "Refresh":
|
||||
if reward.Amount == 0 {
|
||||
return nil, nil, fmt.Errorf("CW Orb refresh amount is missing")
|
||||
}
|
||||
shop.OPDCMCFOFAA, err = add(shop.OPDCMCFOFAA, reward.Amount)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
updates = append(updates, cwShopSync(shop))
|
||||
drop.FKKGLFOHGGA = pb.MJJIFGINJAJ_MJJIFGINJAJ_LJBCDJNNBJH
|
||||
drop.Num = reward.Amount
|
||||
case "GlobalHp":
|
||||
if reward.Amount == 0 || player.GridFightLineupHp > player.PPKKHHNEOHB {
|
||||
return nil, nil, fmt.Errorf("CW Orb HP amount/cap invalid")
|
||||
}
|
||||
gain := min(reward.Amount, player.PPKKHHNEOHB-player.GridFightLineupHp)
|
||||
player.GridFightLineupHp += gain
|
||||
updates = append(updates, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_GridFightLineupHp{GridFightLineupHp: &pb.FMJDDIHBELC{GridFightLineupHp: player.GridFightLineupHp, PPKKHHNEOHB: player.PPKKHHNEOHB}}})
|
||||
drop.FKKGLFOHGGA = pb.MJJIFGINJAJ_MJJIFGINJAJ_grid_fight_lineup_hp
|
||||
drop.Num = gain
|
||||
case "RandomEquip":
|
||||
if reward.Count == 0 || reward.Category == "" {
|
||||
return nil, nil, fmt.Errorf("CW Orb equipment count/category missing")
|
||||
}
|
||||
gear, err := common.LoadCurrencyWarsEquipment(c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var candidates []uint32
|
||||
for _, reference := range recipes.Equipment {
|
||||
if reference.Category == reward.Category {
|
||||
if _, ok := gear[reference.ID]; !ok {
|
||||
return nil, nil, fmt.Errorf("CW Orb equipment %d in category %s is uncaptured", reference.ID, reward.Category)
|
||||
}
|
||||
candidates = append(candidates, reference.ID)
|
||||
}
|
||||
}
|
||||
sort.Slice(candidates, func(i, j int) bool { return candidates[i] < candidates[j] })
|
||||
if len(candidates) == 0 {
|
||||
return nil, nil, fmt.Errorf("CW Orb equipment category %s is uncaptured", reward.Category)
|
||||
}
|
||||
var drops []*pb.BCCONKJHDJL
|
||||
for i := uint32(0); i < reward.Count; i++ {
|
||||
pick, err := rand.Int(rand.Reader, big.NewInt(int64(len(candidates))))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
id := candidates[pick.Int64()]
|
||||
n, err := cwApplyCommand(c, run, common.CurrencyWarsCommand{Field: "equip", Value: id, Quantity: 1})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, group := range n.GCPNIHHBGGD {
|
||||
updates = append(updates, group.JKPJFKCBIEM...)
|
||||
}
|
||||
drops = append(drops, &pb.BCCONKJHDJL{FKKGLFOHGGA: pb.MJJIFGINJAJ_MJJIFGINJAJ_NLFCABFPJHD, FNLMKJLKLFH: id, Num: 1})
|
||||
}
|
||||
return updates, drops, nil
|
||||
case "RandomRole":
|
||||
if reward.Count == 0 || reward.Rarity == 0 {
|
||||
return nil, nil, fmt.Errorf("CW Orb role count/rarity missing")
|
||||
}
|
||||
_, team, shop := cwComponents(run)
|
||||
if team == nil || shop == nil {
|
||||
return nil, nil, fmt.Errorf("CW Orb role/shop components missing")
|
||||
}
|
||||
var drops []*pb.BCCONKJHDJL
|
||||
for i := uint32(0); i < reward.Count; i++ {
|
||||
pool, err := c.NewCapturedCardPool()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, role := range team.GridGameRoleList {
|
||||
if err = pool.ReserveOwned(c, role.Id, role.RoleStar); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
for _, offer := range shop.OGDKJPDFFAI {
|
||||
role := offer.GetBDPIMPJOJBK()
|
||||
if role == nil || offer.LHGIFDNPIDE {
|
||||
continue
|
||||
}
|
||||
if err = pool.ReserveOwned(c, role.RoleId, role.RoleStar); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
var candidates []uint32
|
||||
stars := map[uint32]uint32{}
|
||||
for _, role := range c.CapturedRoles {
|
||||
if role.Config.SeasonID != c.ActiveSeasonID || !role.Config.IsInPool || role.Config.Rarity != reward.Rarity {
|
||||
continue
|
||||
}
|
||||
if stock, ok := pool.Roles[role.Config.ID]; !ok || stock.Disabled || stock.Remaining == 0 {
|
||||
continue
|
||||
}
|
||||
star := reward.Star
|
||||
if star == 0 {
|
||||
star = role.DefaultStar
|
||||
}
|
||||
if _, err = c.CapturedRoleVariant(role.Config.ID, star); err != nil {
|
||||
continue
|
||||
}
|
||||
// ReserveOwned uses captured star rows to count copies; no guessed
|
||||
// power-of-three relationship is introduced for higher-star rewards.
|
||||
candidatePool, err := c.NewCapturedCardPool()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for id, stock := range pool.Roles {
|
||||
candidatePool.Roles[id] = stock
|
||||
}
|
||||
if err = candidatePool.ReserveOwned(c, role.Config.ID, star); err != nil {
|
||||
continue
|
||||
}
|
||||
candidates = append(candidates, role.Config.ID)
|
||||
stars[role.Config.ID] = star
|
||||
}
|
||||
sort.Slice(candidates, func(i, j int) bool { return candidates[i] < candidates[j] })
|
||||
if len(candidates) == 0 {
|
||||
return nil, nil, fmt.Errorf("CW Orb rarity %d/star %d has no available captured role", reward.Rarity, reward.Star)
|
||||
}
|
||||
pick, err := rand.Int(rand.Reader, big.NewInt(int64(len(candidates))))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
id := candidates[pick.Int64()]
|
||||
n, err := cwApplyCommand(c, run, common.CurrencyWarsCommand{Field: "role", Value: id, Star: stars[id], Quantity: 1})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, group := range n.GCPNIHHBGGD {
|
||||
updates = append(updates, group.JKPJFKCBIEM...)
|
||||
}
|
||||
drops = append(drops, &pb.BCCONKJHDJL{FKKGLFOHGGA: pb.MJJIFGINJAJ_MJJIFGINJAJ_BDPIMPJOJBK, FNLMKJLKLFH: id, Num: 1, DisplayValue: &pb.AOHKKFHDALO{NEBNJDMFELO: &pb.AOHKKFHDALO_RoleStar{RoleStar: stars[id]}}})
|
||||
}
|
||||
return updates, drops, nil
|
||||
case "Item":
|
||||
if reward.ItemID == 0 || reward.Count == 0 {
|
||||
return nil, nil, fmt.Errorf("CW Orb item identity/count missing")
|
||||
}
|
||||
gear, err := common.LoadCurrencyWarsEquipment(c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
rewards, err := common.LoadCurrencyWarsRewards(c)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
field := ""
|
||||
if _, ok := gear[reward.ItemID]; ok {
|
||||
field = "equip"
|
||||
} else if _, ok := rewards.Consumables[reward.ItemID]; ok {
|
||||
field = "consumable"
|
||||
} else if _, ok := rewards.Forges[reward.ItemID]; ok {
|
||||
field = "forge"
|
||||
}
|
||||
if field == "" {
|
||||
return nil, nil, fmt.Errorf("CW Orb item %d has no captured acquisition path", reward.ItemID)
|
||||
}
|
||||
n, err := cwApplyCommand(c, run, common.CurrencyWarsCommand{Field: field, Value: reward.ItemID, Quantity: reward.Count})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, group := range n.GCPNIHHBGGD {
|
||||
updates = append(updates, group.JKPJFKCBIEM...)
|
||||
}
|
||||
drop.FKKGLFOHGGA = pb.MJJIFGINJAJ_MJJIFGINJAJ_NLFCABFPJHD
|
||||
drop.FNLMKJLKLFH = reward.ItemID
|
||||
drop.Num = reward.Count
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("CW Orb reward %s is unsupported", reward.Reward)
|
||||
}
|
||||
return updates, []*pb.BCCONKJHDJL{drop}, nil
|
||||
}
|
||||
Reference in new issue
Block a user