329 lines
10 KiB
Go
329 lines
10 KiB
Go
package gameserver
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"maps"
|
|
"math/big"
|
|
"sort"
|
|
|
|
"gac-hkrpg-go/internal/common"
|
|
pb "gac-hkrpg-go/protocol/proto"
|
|
)
|
|
|
|
// Grant transactions operate on the detached Preparation snapshot, then the
|
|
// caller saves before sending the same acquisition updates used by Supply/shop.
|
|
func cwApplyCommand(c *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, cmd common.CurrencyWarsCommand, rt ...*common.CurrencyWarsRuntimeState) (*pb.GridFightSyncUpdateResultScNotify, error) {
|
|
player, team, shop := cwComponents(run)
|
|
if player == nil {
|
|
return nil, fmt.Errorf("CW player missing")
|
|
}
|
|
switch cmd.Field {
|
|
case "gold":
|
|
player.Gold = cmd.Value
|
|
return cwNotify(cwGoldSync(player.Gold)), nil
|
|
case "tbpower", "tb", "trailblaze":
|
|
tbCat, err := common.LoadCurrencyWarsTrailblaze()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var runtime *common.CurrencyWarsRuntimeState
|
|
if len(rt) > 0 && rt[0] != nil {
|
|
runtime = rt[0]
|
|
} else {
|
|
runtime, _ = cwBonfireRuntime(c, run)
|
|
}
|
|
if runtime == nil {
|
|
runtime = &common.CurrencyWarsRuntimeState{RunID: run.UniqueId}
|
|
}
|
|
if runtime.Rules == nil {
|
|
runtime.Rules = make(map[string]uint32)
|
|
}
|
|
layer := cwGetTrailblazeLayer(c, run)
|
|
tier := tbCat.Tier(layer)
|
|
if runtime.Rules["TbOpened"] == 0 {
|
|
runtime.Rules["TbOpened"] = 1
|
|
stage, _ := cwStage(run)
|
|
if stage != nil {
|
|
runtime.Rules["TbNode"] = stage.DCAHJELNJNP*1000 + stage.SectionId
|
|
}
|
|
cwTrailblazeRoll(tbCat, run, runtime, tier)
|
|
if tbBoughtCount(runtime, tbCat.InitialGoodID) == 0 {
|
|
runtime.Rules["TbGood/0"] = tbCat.InitialGoodID
|
|
}
|
|
}
|
|
runtime.Rules["TbPower"] = cmd.Value
|
|
if cmd.Star > 0 {
|
|
runtime.Rules["TbCosted"] = cmd.Star - 1
|
|
if runtime.Rules["TbCosted"] >= tbCat.PomThreshold && !tbOffered(runtime, tbCat.PomGoodID) {
|
|
slotToPut := 0
|
|
for s := 0; s < cwTrailblazeSlots; s++ {
|
|
if runtime.Rules[fmt.Sprintf("TbGood/%d", s)] == 0 {
|
|
slotToPut = s
|
|
break
|
|
}
|
|
}
|
|
runtime.Rules[fmt.Sprintf("TbGood/%d", slotToPut)] = tbCat.PomGoodID
|
|
}
|
|
}
|
|
|
|
cwSyncTraitComponent(c, run, runtime)
|
|
|
|
bundle := &pb.ABPLALCHNFA{
|
|
GridUpdateSrc: pb.JJDFGMMMCMI_JJDFGMMMCMI_GLKAGBGLENM,
|
|
OOEOCGLLHFO: []uint32{cwTrailblazeTrait, cwTrailblazeEffect},
|
|
JKPJFKCBIEM: []*pb.KFNCAIHIHCM{
|
|
{
|
|
MNLCHEJKDHO: &pb.KFNCAIHIHCM_HKHJJCMKKJE{
|
|
HKHJJCMKKJE: &pb.JLKCBOIFJKC{
|
|
TraitId: cwTrailblazeTrait,
|
|
ENPLACDLJGA: cwTrailblazeTraitEffectProto(tbCat, runtime),
|
|
},
|
|
},
|
|
},
|
|
{
|
|
MNLCHEJKDHO: &pb.KFNCAIHIHCM_GOOCHGKBIPN{
|
|
GOOCHGKBIPN: cwTrailblazeModifierProto(tbCat, runtime),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
return &pb.GridFightSyncUpdateResultScNotify{
|
|
GCPNIHHBGGD: []*pb.ABPLALCHNFA{bundle},
|
|
}, nil
|
|
case "hp":
|
|
if cmd.Value == 0 || cmd.Value > player.PPKKHHNEOHB {
|
|
return nil, fmt.Errorf("HP must be 1..%d", player.PPKKHHNEOHB)
|
|
}
|
|
player.GridFightLineupHp = cmd.Value
|
|
return cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_GridFightLineupHp{GridFightLineupHp: &pb.FMJDDIHBELC{GridFightLineupHp: cmd.Value, PPKKHHNEOHB: player.PPKKHHNEOHB}}}), nil
|
|
case "exp":
|
|
if shop == nil {
|
|
return nil, fmt.Errorf("CW shop missing")
|
|
}
|
|
if err := cwApplyBaseExperience(c, player, shop, cmd.Value); err != nil {
|
|
return nil, err
|
|
}
|
|
return cwNotify(cwCapacitySync(player), cwShopSync(shop), &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PLPHEIBLPDL{PLPHEIBLPDL: &pb.PIHKLPJGEPM{Level: player.EHNJDIDABHD, Exp: player.GridFightCurLevelExp, HFMKLAPEKFA: player.NDAINFLOLPJ}}}), nil
|
|
case "equip":
|
|
items, err := common.LoadCurrencyWarsEquipment(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, ok := items[cmd.Value]; !ok {
|
|
return nil, fmt.Errorf("equipment %d not captured", cmd.Value)
|
|
}
|
|
bag := cwEquipmentBag(run)
|
|
if bag == nil {
|
|
return nil, fmt.Errorf("CW equipment bag missing")
|
|
}
|
|
quantity := cmd.Quantity
|
|
if quantity == 0 {
|
|
quantity = 1
|
|
} // Existing scalar queue format.
|
|
if uint64(quantity) > uint64(^uint32(0))-uint64(len(bag.GridFightEquipmentList)) {
|
|
return nil, fmt.Errorf("equipment count overflow")
|
|
}
|
|
added := &pb.AOFOEOIJEHF{}
|
|
for i := uint32(0); i < quantity; i++ {
|
|
uid, e := cwNextEntityID(run)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
// Source zero is protocol Default. Do not impersonate a Supply reward.
|
|
gear := &pb.IJDLENJJBHJ{UniqueId: uid, GridFightEquipmentId: cmd.Value}
|
|
bag.GridFightEquipmentList = append(bag.GridFightEquipmentList, gear)
|
|
added.GridFightEquipmentList = append(added.GridFightEquipmentList, gear)
|
|
}
|
|
return cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_LMOLELDIKMA{LMOLELDIKMA: added}}), nil
|
|
case "orb":
|
|
rewards, err := common.LoadCurrencyWarsRewards(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var pool []uint32
|
|
if cmd.Value == 0 {
|
|
recipes, err := common.LoadCurrencyWarsOrbRecipes()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, r := range recipes.Recipes {
|
|
if r.Status == "Implemented" && r.Engine == "Orb" {
|
|
if _, ok := rewards.Orbs[r.ID]; ok {
|
|
pool = append(pool, r.ID)
|
|
}
|
|
}
|
|
}
|
|
if len(pool) == 0 {
|
|
return nil, fmt.Errorf("no implemented CW orbs found")
|
|
}
|
|
sort.Slice(pool, func(i, j int) bool { return pool[i] < pool[j] })
|
|
} else {
|
|
if _, ok := rewards.Orbs[cmd.Value]; !ok {
|
|
return nil, fmt.Errorf("Orb %d not captured", cmd.Value)
|
|
}
|
|
}
|
|
var orbs *pb.DBONCGFHHDN
|
|
for _, component := range run.NAGDJGMMCBE {
|
|
if x := component.GetDBAGKJNPDOL(); x != nil {
|
|
orbs = x
|
|
}
|
|
}
|
|
if orbs == nil {
|
|
orbs = &pb.DBONCGFHHDN{}
|
|
run.NAGDJGMMCBE = append(run.NAGDJGMMCBE, &pb.GridFightGameInfo{
|
|
LEOIAOFBBFA: &pb.GridFightGameInfo_DBAGKJNPDOL{
|
|
DBAGKJNPDOL: orbs,
|
|
},
|
|
})
|
|
}
|
|
quantity := cmd.Quantity
|
|
if quantity == 0 {
|
|
quantity = 1
|
|
}
|
|
notify := cwNotify()
|
|
for i := uint32(0); i < quantity; i++ {
|
|
orbID := cmd.Value
|
|
if orbID == 0 {
|
|
idx, err := rand.Int(rand.Reader, big.NewInt(int64(len(pool))))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
orbID = pool[idx.Int64()]
|
|
}
|
|
uid, e := cwNextEntityID(run)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
orbs.MDKGCIKOMLI = append(orbs.MDKGCIKOMLI, &pb.IBPDPCOGDEP{UniqueId: uid, LDBNOMPCOKK: orbID})
|
|
notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_AFILIPBHMOO{AFILIPBHMOO: &pb.MMEDLCAENAL{UniqueId: uid, LDBNOMPCOKK: orbID}}})
|
|
}
|
|
return notify, nil
|
|
case "consumable":
|
|
rewards, err := common.LoadCurrencyWarsRewards(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, ok := rewards.Consumables[cmd.Value]; !ok {
|
|
return nil, fmt.Errorf("consumable %d not captured", cmd.Value)
|
|
}
|
|
bag := cwEquipmentBag(run)
|
|
if bag == nil {
|
|
return nil, fmt.Errorf("CW bag missing")
|
|
}
|
|
quantity := cmd.Quantity
|
|
if quantity == 0 {
|
|
quantity = 1
|
|
}
|
|
if quantity > uint32(^uint32(0)>>1) {
|
|
return nil, fmt.Errorf("consumable delta exceeds protocol int32")
|
|
}
|
|
var row *pb.LALKBPJANIH
|
|
var group uint32
|
|
for _, x := range bag.IPMKOMEHBMP {
|
|
if x.GroupId > group {
|
|
group = x.GroupId
|
|
}
|
|
if x.ItemId == cmd.Value {
|
|
row = x
|
|
}
|
|
}
|
|
fresh := row == nil
|
|
if fresh {
|
|
if group == ^uint32(0) {
|
|
return nil, fmt.Errorf("consumable groups exhausted")
|
|
}
|
|
row = &pb.LALKBPJANIH{GroupId: group + 1, ItemId: cmd.Value}
|
|
bag.IPMKOMEHBMP = append(bag.IPMKOMEHBMP, row)
|
|
}
|
|
if uint64(row.Num)+uint64(quantity) > uint64(^uint32(0)) {
|
|
return nil, fmt.Errorf("consumable stack overflow")
|
|
}
|
|
row.Num += quantity
|
|
info := &pb.AOFOEOIJEHF{PFMDKHIDDLB: []*pb.FLGMGNEOFBH{{GroupId: row.GroupId, ItemId: row.ItemId, Num: row.Num, RoundCnt: row.RoundCnt, HFNNNGHBHGG: int32(quantity)}}}
|
|
update := &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_DDBKEOPKPFI{DDBKEOPKPFI: info}}
|
|
if fresh {
|
|
update.MNLCHEJKDHO = &pb.KFNCAIHIHCM_LMOLELDIKMA{LMOLELDIKMA: info}
|
|
}
|
|
return cwNotify(update), nil
|
|
case "forge":
|
|
quantity := cmd.Quantity
|
|
if quantity == 0 {
|
|
quantity = 1
|
|
}
|
|
notify := cwNotify()
|
|
for i := uint32(0); i < quantity; i++ {
|
|
update, err := cwGrantForge(c, run, cmd.Value)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
notify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(notify.GCPNIHHBGGD[0].JKPJFKCBIEM, update)
|
|
}
|
|
return notify, nil
|
|
case "role":
|
|
if team == nil || len(team.GridGameRoleList) == 0 {
|
|
return nil, fmt.Errorf("CW role property template missing")
|
|
}
|
|
if _, err := c.CapturedRoleVariant(cmd.Value, cmd.Star); err != nil {
|
|
return nil, err
|
|
}
|
|
scales := team.GridGameRoleList[0].ConvertPropertyToFixpoint
|
|
for _, r := range team.GridGameRoleList {
|
|
if !maps.Equal(scales, r.ConvertPropertyToFixpoint) {
|
|
return nil, fmt.Errorf("role property templates differ")
|
|
}
|
|
}
|
|
front, err := c.IntegerRule("GridFight_Front_AvatarMaxNum")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
back, err := c.IntegerRule("GridFight_Back_AvatarMaxNum")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
quantity := cmd.Quantity
|
|
if quantity == 0 {
|
|
quantity = 1
|
|
}
|
|
notify := &pb.GridFightSyncUpdateResultScNotify{}
|
|
for i := uint32(0); i < quantity; i++ {
|
|
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) + uint64(back) + 1; p <= uint64(front)+uint64(back)+uint64(player.MICEOJBAKGN) && p <= uint64(^uint32(0)); p++ {
|
|
if !occupied[uint32(p)] {
|
|
pos = uint32(p)
|
|
break
|
|
}
|
|
}
|
|
uid, e := cwNextEntityID(run)
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
role := &pb.CDEAABODKCP{Id: cmd.Value, RoleStar: cmd.Star, UniqueId: uid, Pos: pos, ConvertPropertyToFixpoint: maps.Clone(scales)}
|
|
team.GridGameRoleList = append(team.GridGameRoleList, role)
|
|
notify.GCPNIHHBGGD = append(notify.GCPNIHHBGGD, cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PIDDDHKPDDM{PIDDDHKPDDM: role}}).GCPNIHHBGGD...)
|
|
merged, e := cwMergeRoles(c, run, uid)
|
|
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 bench full; entire grant cancelled")
|
|
}
|
|
}
|
|
}
|
|
return notify, nil
|
|
}
|
|
return nil, fmt.Errorf("unknown CW command %q", cmd.Field)
|
|
}
|