Files
GaCy-GO/internal/gameserver/currency_wars_supply_offer.go
T

168 lines
5.6 KiB
Go

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 {
if _, ok := gear[item.ID]; ok {
equipment = append(equipment, item.ID)
}
}
}
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
}