2026-10-30 19:19:05 +07:00
|
|
|
package gameserver
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"math/rand/v2"
|
|
|
|
|
"sort"
|
|
|
|
|
|
|
|
|
|
"gac-hkrpg-go/internal/common"
|
|
|
|
|
pb "gac-hkrpg-go/protocol/proto"
|
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// cwGenerateSupplyOffer follows the Supply option factory in the reference
|
|
|
|
|
// executable identified in docs/cw-route-continuation-audit.md. The protocol
|
|
|
|
|
// envelope comes from the native capture; role and equipment IDs must also be
|
|
|
|
|
// present in this client's captured data.
|
|
|
|
|
func cwGenerateSupplyOffer(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH) (*pb.IAKEDFLIOFD, error) {
|
|
|
|
|
stage, _ := cwStage(run)
|
|
|
|
|
if stage == nil || stage.DCAHJELNJNP == 0 {
|
|
|
|
|
return nil, fmt.Errorf("CW Supply chapter missing")
|
|
|
|
|
}
|
|
|
|
|
runtime, err := cwBonfireRuntime(c, run)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return cwGenerateSupplyOfferWithRules(c, run, runtime.Rules)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func cwGenerateSupplyOfferWithRules(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, rules map[string]uint32) (*pb.IAKEDFLIOFD, error) {
|
|
|
|
|
stage, _ := cwStage(run)
|
|
|
|
|
if stage == nil || stage.DCAHJELNJNP == 0 {
|
|
|
|
|
return nil, fmt.Errorf("CW Supply chapter missing")
|
|
|
|
|
}
|
|
|
|
|
// Box modifies a different part of the selection flow; do not silently
|
|
|
|
|
// grant an ordinary selection if the corresponding action is not mapped.
|
|
|
|
|
if rules["SupplyBox"] != 0 {
|
|
|
|
|
return nil, fmt.Errorf("CW SupplyBox action is not mapped")
|
|
|
|
|
}
|
|
|
|
|
count, err := c.IntegerRule("GridFight_BaseSupplyRoleNum")
|
|
|
|
|
// gfFxHProjectRunState projects ExtraSupplyOption into the field added by
|
|
|
|
|
// gfPendingFillSupplyOptions to the Client's captured base count.
|
|
|
|
|
if err != nil || count <= 0 || uint64(count)+uint64(rules["ExtraSupplyOption"]) > uint64(len(c.CapturedRoles)) {
|
|
|
|
|
return nil, fmt.Errorf("CW Supply option count invalid")
|
|
|
|
|
}
|
|
|
|
|
count += int64(rules["ExtraSupplyOption"])
|
|
|
|
|
star := rules["SupplyStar"]
|
|
|
|
|
if star == 0 {
|
|
|
|
|
star = 1
|
|
|
|
|
} // gfPendingSupplyOf clamps this rule to one.
|
|
|
|
|
category := "Basic"
|
|
|
|
|
if rules["SupplyEmblems"] != 0 {
|
|
|
|
|
category = "Emblem"
|
|
|
|
|
} else if stage.DCAHJELNJNP >= 3 {
|
|
|
|
|
category = "Craftable"
|
|
|
|
|
}
|
|
|
|
|
recipes, err := common.LoadCurrencyWarsOrbRecipes()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
gear, err := common.LoadCurrencyWarsEquipment(c)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
var equipment []uint32
|
|
|
|
|
for _, item := range recipes.Equipment {
|
|
|
|
|
if item.Category == category {
|
2026-10-31 20:47:36 +07:00
|
|
|
if _, ok := gear[item.ID]; ok {
|
|
|
|
|
equipment = append(equipment, item.ID)
|
2026-10-30 19:19:05 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if len(equipment) == 0 {
|
|
|
|
|
return nil, fmt.Errorf("CW Supply equipment category %s is empty", category)
|
|
|
|
|
}
|
|
|
|
|
sort.Slice(equipment, func(i, j int) bool { return equipment[i] < equipment[j] })
|
|
|
|
|
pool, err := c.NewCapturedCardPool()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
byRarity := make(map[uint32][]uint32)
|
|
|
|
|
for id, item := range pool.Roles {
|
|
|
|
|
if item.Disabled || item.Remaining == 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if _, err := c.CapturedRoleVariant(id, star); err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
byRarity[item.Rarity] = append(byRarity[item.Rarity], id)
|
|
|
|
|
}
|
|
|
|
|
for _, ids := range byRarity {
|
|
|
|
|
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
|
|
|
|
}
|
|
|
|
|
entries, err := cwNodeTransitions(c)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
var offer *pb.IAKEDFLIOFD
|
|
|
|
|
for _, entry := range entries {
|
|
|
|
|
pending := new(pb.CHCHAJIANIM)
|
|
|
|
|
if err := proto.Unmarshal(entry.RoundWire, pending); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if captured := pending.GetFIKAGBAEOAP(); captured != nil {
|
|
|
|
|
candidate := proto.Clone(captured).(*pb.IAKEDFLIOFD)
|
|
|
|
|
candidate.MFBAKLCBPCJ = nil
|
|
|
|
|
if offer != nil && !proto.Equal(offer, candidate) {
|
|
|
|
|
return nil, fmt.Errorf("CW Supply envelopes differ")
|
|
|
|
|
}
|
|
|
|
|
offer = candidate
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if offer == nil {
|
|
|
|
|
return nil, fmt.Errorf("CW Supply envelope not captured")
|
|
|
|
|
}
|
|
|
|
|
used := map[uint32]bool{}
|
|
|
|
|
for i := int64(0); i < int64(count); i++ {
|
|
|
|
|
var id uint32
|
|
|
|
|
for attempt := 0; attempt < 8; attempt++ {
|
|
|
|
|
rarity := rules["SupplyRarity"]
|
|
|
|
|
if rarity == 0 {
|
|
|
|
|
rarity = min(uint32(rand.IntN(int(stage.DCAHJELNJNP+2)))+1, uint32(5))
|
|
|
|
|
}
|
|
|
|
|
// gfPoolRandomRoleFiltered searches the requested rarity first,
|
|
|
|
|
// then neighbouring rarity buckets at increasing distance.
|
|
|
|
|
for distance := uint32(0); distance < 5 && id == 0; distance++ {
|
|
|
|
|
for _, candidate := range []int64{int64(rarity) - int64(distance), int64(rarity) + int64(distance)} {
|
|
|
|
|
if candidate < 1 || candidate > 5 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
ids := byRarity[uint32(candidate)]
|
|
|
|
|
if len(ids) != 0 {
|
|
|
|
|
id = ids[rand.IntN(len(ids))]
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if id != 0 && !used[id] {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
id = 0
|
|
|
|
|
}
|
|
|
|
|
if id == 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
used[id] = true
|
|
|
|
|
item := equipment[rand.IntN(len(equipment))]
|
|
|
|
|
offer.MFBAKLCBPCJ = append(offer.MFBAKLCBPCJ, &pb.NBGDEFLFLMI{RoleId: id, OPPFPECOBEF: &pb.BCCONKJHDJL{FKKGLFOHGGA: pb.MJJIFGINJAJ_MJJIFGINJAJ_BDPIMPJOJBK, FNLMKJLKLFH: id, Num: 1, DisplayValue: &pb.AOHKKFHDALO{NEBNJDMFELO: &pb.AOHKKFHDALO_RoleStar{RoleStar: star}}}, GridFightItemList: []uint32{item}, DHLBEFLOOPM: []*pb.BCCONKJHDJL{{FKKGLFOHGGA: pb.MJJIFGINJAJ_MJJIFGINJAJ_NLFCABFPJHD, FNLMKJLKLFH: item, Num: 1}}})
|
|
|
|
|
}
|
|
|
|
|
if offer.DAEMEBCKHIK == 0 || uint64(len(offer.MFBAKLCBPCJ)) < uint64(offer.DAEMEBCKHIK) {
|
|
|
|
|
return nil, fmt.Errorf("CW Supply has too few valid role options")
|
|
|
|
|
}
|
|
|
|
|
chance, item := rules["SupplyEquipChance"], rules["SupplyEquip"]
|
|
|
|
|
if chance > 100 {
|
|
|
|
|
return nil, fmt.Errorf("CW Supply equipment chance invalid")
|
|
|
|
|
}
|
|
|
|
|
if chance != 0 && item != 0 && stage.DCAHJELNJNP >= rules["SupplyEquipChapter"] {
|
|
|
|
|
if _, ok := gear[item]; !ok {
|
|
|
|
|
return nil, fmt.Errorf("CW Supply bonus equipment %d is not captured", item)
|
|
|
|
|
}
|
|
|
|
|
if uint32(rand.IntN(100)) < chance {
|
|
|
|
|
choice := offer.MFBAKLCBPCJ[rand.IntN(len(offer.MFBAKLCBPCJ))]
|
|
|
|
|
choice.GridFightItemList[0] = item
|
|
|
|
|
choice.DHLBEFLOOPM[0].FNLMKJLKLFH = item
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return offer, nil
|
|
|
|
|
}
|