Files
GaCy-GO/internal/gameserver/currency_wars_supply.go
2026-10-28 18:54:43 +07:00

188 lines
7.2 KiB
Go

package gameserver
import (
"fmt"
"maps"
"gac-hkrpg-go/internal/common"
pb "gac-hkrpg-go/protocol/proto"
"google.golang.org/protobuf/proto"
)
// Native Supply selection capture march-cw-supply-next: equipment Source=9.
// This is the protocol acquisition-source discriminator, not a game item ID.
const cwEquipmentSourceSupply uint32 = 9
// Applies only to the detached pending-action snapshot; the caller saves the
// complete transaction before publishing any grant or queue acknowledgement.
func cwSelectSupply(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, request *pb.HMNMJKCHABE) (*pb.GridFightSyncUpdateResultScNotify, error) {
pending := run.GetFMIGOKIDKJL()
offer := pending.GetFIKAGBAEOAP()
if offer == nil || request == nil || offer.DAEMEBCKHIK == 0 || uint64(len(request.LOAFKKAFBOK)) != uint64(offer.DAEMEBCKHIK) {
return nil, fmt.Errorf("CW Supply selection count differs from offer")
}
// Paid additional selections have no verified cost/update sequence yet.
if offer.JJPMDBCPJBA < offer.DAEMEBCKHIK || offer.PLPJNCDKMAD != 0 {
return nil, fmt.Errorf("CW paid Supply selection is not captured")
}
player, team, _ := cwComponents(run)
stage, node := cwStage(run)
var bag *pb.CONONEALNOF
for _, c := range run.NAGDJGMMCBE {
if v := c.GetGCKLIONKMJC(); v != nil {
bag = v
}
}
if player == nil || team == nil || stage == nil || node == nil || bag == nil || len(team.GridGameRoleList) == 0 {
return nil, fmt.Errorf("CW Supply components missing")
}
equipment, err := common.LoadCurrencyWarsEquipment(catalog)
if err != nil {
return nil, err
}
front, err := catalog.IntegerRule("GridFight_Front_AvatarMaxNum")
if err != nil {
return nil, err
}
back, err := catalog.IntegerRule("GridFight_Back_AvatarMaxNum")
if err != nil {
return nil, err
}
overflow, err := catalog.IntegerRule("GridFight_Bench_OverFlow_AvatarNum")
if err != nil {
return nil, err
}
scales := team.GridGameRoleList[0].ConvertPropertyToFixpoint
for _, role := range team.GridGameRoleList {
if !maps.Equal(scales, role.ConvertPropertyToFixpoint) {
return nil, fmt.Errorf("CW Supply role property template differs")
}
}
notify := &pb.GridFightSyncUpdateResultScNotify{}
used := map[uint32]bool{}
for _, index := range request.LOAFKKAFBOK {
// The native action stores one-based option indices.
if index == 0 || uint64(index) > uint64(len(offer.MFBAKLCBPCJ)) || used[index] {
return nil, fmt.Errorf("CW Supply option invalid/duplicated")
}
used[index] = true
choice := offer.MFBAKLCBPCJ[index-1]
if choice == nil {
return nil, fmt.Errorf("CW Supply option missing")
}
drop := choice.GetOPPFPECOBEF()
if drop == nil || drop.FKKGLFOHGGA != pb.MJJIFGINJAJ_MJJIFGINJAJ_BDPIMPJOJBK || drop.FNLMKJLKLFH != choice.RoleId || drop.Num != 1 {
return nil, fmt.Errorf("CW Supply role drop differs from display")
}
star := drop.GetDisplayValue().GetRoleStar()
if _, err = catalog.CapturedRoleVariant(choice.RoleId, star); err != nil {
return nil, err
}
occupied := map[uint32]bool{}
for _, r := range team.GridGameRoleList {
occupied[r.Pos] = true
}
for _, r := range team.KCOMPKMJCMC {
occupied[r.Pos] = true
}
for _, f := range team.PMEOPPDAMLM {
occupied[f.Pos] = true
}
var pos uint32
for p := uint64(front+back) + 1; p <= uint64(front+back)+uint64(player.MICEOJBAKGN)+uint64(overflow); p++ {
if p > uint64(^uint32(0)) {
break
}
if !occupied[uint32(p)] {
pos = uint32(p)
break
}
}
id, e := cwNextEntityID(run)
if e != nil {
return nil, e
}
role := &pb.CDEAABODKCP{Id: choice.RoleId, RoleStar: star, UniqueId: id, Pos: pos, ConvertPropertyToFixpoint: maps.Clone(scales)}
team.GridGameRoleList = append(team.GridGameRoleList, role)
// Reference sync orders equipment acquisition, role acquisition, then
// merges as separate result groups sharing the selected option context.
grants := cwNotify()
grants.GCPNIHHBGGD[0].GridUpdateSrc = pb.JJDFGMMMCMI_JJDFGMMMCMI_GLKAGBGLENM
grants.GCPNIHHBGGD[0].OOEOCGLLHFO = []uint32{index}
if len(choice.DHLBEFLOOPM) != len(choice.GridFightItemList) {
return nil, fmt.Errorf("CW Supply equipment display differs")
}
for i, item := range choice.DHLBEFLOOPM {
if item == nil || item.FKKGLFOHGGA != pb.MJJIFGINJAJ_MJJIFGINJAJ_NLFCABFPJHD || item.Num != 1 || item.FNLMKJLKLFH != choice.GridFightItemList[i] {
return nil, fmt.Errorf("CW Supply equipment drop differs")
}
if _, ok := equipment[item.FNLMKJLKLFH]; !ok {
return nil, fmt.Errorf("CW Supply equipment %d not captured", item.FNLMKJLKLFH)
}
uid, e := cwNextEntityID(run)
if e != nil {
return nil, e
}
gear := &pb.IJDLENJJBHJ{UniqueId: uid, GridFightEquipmentId: item.FNLMKJLKLFH, Source: cwEquipmentSourceSupply}
bag.GridFightEquipmentList = append(bag.GridFightEquipmentList, gear)
grants.GCPNIHHBGGD[0].JKPJFKCBIEM = append(grants.GCPNIHHBGGD[0].JKPJFKCBIEM, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_LMOLELDIKMA{LMOLELDIKMA: &pb.AOFOEOIJEHF{GridFightEquipmentList: []*pb.IJDLENJJBHJ{gear}}}})
}
notify.GCPNIHHBGGD = append(notify.GCPNIHHBGGD, grants.GCPNIHHBGGD...)
roles := cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PIDDDHKPDDM{PIDDDHKPDDM: role}})
roles.GCPNIHHBGGD[0].GridUpdateSrc = pb.JJDFGMMMCMI_JJDFGMMMCMI_GLKAGBGLENM
roles.GCPNIHHBGGD[0].OOEOCGLLHFO = []uint32{index}
notify.GCPNIHHBGGD = append(notify.GCPNIHHBGGD, roles.GCPNIHHBGGD...)
merged, e := cwMergeRoles(catalog, run, id)
if e != nil {
return nil, e
}
notify.GCPNIHHBGGD = append(notify.GCPNIHHBGGD, merged...)
for _, r := range team.GridGameRoleList {
if r.Pos == 0 {
return nil, fmt.Errorf("CW Supply bench is full")
}
}
}
entries, err := cwNodeTransitions(catalog)
if err != nil {
return nil, err
}
var next *cwNodeTransition
for i := range entries {
e := &entries[i]
if e.Route == stage.EMOHLJHPOPC && e.Chapter == stage.DCAHJELNJNP && e.Section == stage.SectionId && e.Stage == node.StageId && e.Penalty == node.IFIIOIIKJHO && e.Division == run.DivisionId && e.Overclock == run.IsOverlock {
if next != nil {
return nil, fmt.Errorf("CW Supply transition duplicated")
}
next = e
}
}
if next == nil {
return nil, fmt.Errorf("CW Supply next node not captured")
}
level := new(pb.AINFNFIDGFF)
nextPending := new(pb.CHCHAJIANIM)
if err = proto.Unmarshal(next.LevelWire, level); err != nil {
return nil, err
}
if err = proto.Unmarshal(next.PendingWire, nextPending); err != nil {
return nil, err
}
if level.IMPNAHBCADN == nil || nextPending.GetNNDGEJFLGGF() == nil {
return nil, fmt.Errorf("CW Supply next node incomplete")
}
found := false
for _, n := range stage.OOMMIIKPHBC {
found = found || (n.DCAHJELNJNP == level.DCAHJELNJNP && n.SectionId == level.SectionId)
}
if !found {
return nil, fmt.Errorf("CW Supply next node outside route")
}
stage.DCAHJELNJNP = level.DCAHJELNJNP
stage.SectionId = level.SectionId
stage.IMPNAHBCADN = proto.Clone(level.IMPNAHBCADN).(*pb.NCKOKDHNJDC)
run.FMIGOKIDKJL = nextPending
notify.GCPNIHHBGGD = append(notify.GCPNIHHBGGD, cwNotify(cwFinish(pending.QueuePosition), cwUnlock(), &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_OOICHLAMNIG{OOICHLAMNIG: level}}, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: nextPending}}).GCPNIHHBGGD...)
return notify, nil
}