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

477 lines
14 KiB
Go
Raw Normal View History

package gameserver
import (
"fmt"
"maps"
"math/rand/v2"
"strconv"
"strings"
"gac-hkrpg-go/internal/common"
pb "gac-hkrpg-go/protocol/proto"
)
const (
cwTrailblazeTrait uint32 = 100502
cwTrailblazeEffect uint32 = 1005021
cwTrailblazeModifierUID uint32 = 1005021
cwTrailblazeSlots int = 4
)
// cwGetTrailblazeLayer returns the active layer (2, 4, 6, 8, 10 or 0) of the Trailblaze Silver Rail trait.
func cwGetTrailblazeLayer(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH) uint32 {
traits := cwCalculateBoardTraits(catalog, run)
for _, t := range traits {
if t.TraitId == cwTrailblazeTrait {
return t.KOJFHFAKKFO
}
}
return 0
}
func cwTrailblazeActive(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState) bool {
if runtime != nil && (runtime.Rules["TbOpened"] != 0 || runtime.Rules["TbPower"] > 0) {
return true
}
return cwGetTrailblazeLayer(catalog, run) > 0
}
func tbOffered(runtime *common.CurrencyWarsRuntimeState, goodID uint32) bool {
if runtime == nil {
return false
}
for slot := 0; slot < cwTrailblazeSlots; slot++ {
if runtime.Rules[fmt.Sprintf("TbGood/%d", slot)] == goodID {
return true
}
}
return false
}
func tbBoughtCount(runtime *common.CurrencyWarsRuntimeState, goodID uint32) uint32 {
if runtime == nil {
return 0
}
return runtime.Rules[fmt.Sprintf("TbBought/%d", goodID)]
}
func tbIsEligible(tbCat *common.CurrencyWarsTrailblazeCatalog, runtime *common.CurrencyWarsRuntimeState, row common.CurrencyWarsTrailblazeGood, tier uint32) bool {
if row.Kind == "refresh" || row.ID == tbCat.InitialGoodID {
return false
}
if row.ID/100 > tier+1 {
return false
}
if row.Father != 0 && tbBoughtCount(runtime, row.Father) == 0 {
return false
}
if row.Kind == "maze_buff" && tbBoughtCount(runtime, row.ID) != 0 {
return false
}
pomItem := row.Kind == "item" && len(row.Params) == 1 && (row.Params[0] == 353201 || row.Params[0] == 353202)
pomUnlocked := runtime != nil && runtime.Rules["TbCosted"] >= tbCat.PomThreshold
if (row.ID == tbCat.PomGoodID || pomItem) && !pomUnlocked {
return false
}
return !tbOffered(runtime, row.ID)
}
func cwTrailblazeRNG(run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState) *rand.Rand {
var runID uint64 = 1
var nodeKey uint64 = 0
if run != nil {
runID = uint64(run.UniqueId)
if stage, _ := cwStage(run); stage != nil {
nodeKey = uint64(stage.DCAHJELNJNP)*10000 + uint64(stage.SectionId)*100
}
}
var refresh uint64 = 0
if runtime != nil && runtime.Rules != nil {
refresh = uint64(runtime.Rules["TbRefreshCount"])
}
seed := runID*1000003 + nodeKey*31 + refresh*7 + 100502
return rand.New(rand.NewPCG(seed, seed^0x5DEECE66D))
}
func cwTrailblazeRoll(tbCat *common.CurrencyWarsTrailblazeCatalog, run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState, tier uint32) {
if runtime == nil || runtime.Rules == nil {
return
}
rng := cwTrailblazeRNG(run, runtime)
for slot := 0; slot < cwTrailblazeSlots; slot++ {
runtime.Rules[fmt.Sprintf("TbGood/%d", slot)] = 0
var eligible []common.CurrencyWarsTrailblazeGood
for _, row := range tbCat.GoodsList {
if tbIsEligible(tbCat, runtime, row, tier) {
eligible = append(eligible, row)
}
}
if len(eligible) == 0 {
continue
}
pick := eligible[rng.IntN(len(eligible))]
runtime.Rules[fmt.Sprintf("TbGood/%d", slot)] = pick.ID
}
}
func cwTrailblazeNodeEntered(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState) {
layer := cwGetTrailblazeLayer(catalog, run)
if layer == 0 {
return
}
tbCat, err := common.LoadCurrencyWarsTrailblaze()
if err != nil {
return
}
tier := tbCat.Tier(layer)
if runtime.Rules == nil {
runtime.Rules = make(map[string]uint32)
}
stage, _ := cwStage(run)
var currentNode uint32
if stage != nil {
currentNode = stage.DCAHJELNJNP*1000 + stage.SectionId
}
if runtime.Rules["TbOpened"] == 0 {
runtime.Rules["TbOpened"] = 1
runtime.Rules["TbNode"] = currentNode
runtime.Rules["TbRefreshCount"] = 0
cwTrailblazeRoll(tbCat, run, runtime, tier)
if tbBoughtCount(runtime, tbCat.InitialGoodID) == 0 {
runtime.Rules["TbGood/0"] = tbCat.InitialGoodID
}
} else if runtime.Rules["TbNode"] != currentNode {
runtime.Rules["TbNode"] = currentNode
runtime.Rules["TbRefreshCount"] = 0
cwTrailblazeRoll(tbCat, run, runtime, tier)
if tbBoughtCount(runtime, tbCat.InitialGoodID) == 0 {
runtime.Rules["TbGood/0"] = tbCat.InitialGoodID
}
}
}
func cwTrailblazeShopProto(tbCat *common.CurrencyWarsTrailblazeCatalog, runtime *common.CurrencyWarsRuntimeState) *pb.GGFHGKGJOOB {
shop := &pb.GGFHGKGJOOB{
OBHKKOFDEFG: make(map[uint32]uint32),
}
if runtime != nil {
shop.MJIGJEGMCML = runtime.Rules["TbPower"]
for slot := 0; slot < cwTrailblazeSlots; slot++ {
gid := runtime.Rules[fmt.Sprintf("TbGood/%d", slot)]
if gid != 0 {
if good, ok := tbCat.Goods[gid]; ok {
shop.OGDKJPDFFAI = append(shop.OGDKJPDFFAI, &pb.AMNAMDLMADK{
GoodsId: gid,
OICGFNGNLOE: good.Cost,
})
}
}
}
for k, v := range runtime.Rules {
if strings.HasPrefix(k, "TbBought/") {
idStr := strings.TrimPrefix(k, "TbBought/")
if gid, err := strconv.ParseUint(idStr, 10, 32); err == nil && v > 0 {
shop.OBHKKOFDEFG[uint32(gid)] = v
}
}
}
}
shop.OGDKJPDFFAI = append(shop.OGDKJPDFFAI, &pb.AMNAMDLMADK{
GoodsId: tbCat.RefreshGoodID,
OICGFNGNLOE: 1,
})
return shop
}
func cwTrailblazeModifierProto(tbCat *common.CurrencyWarsTrailblazeCatalog, runtime *common.CurrencyWarsRuntimeState) *pb.NJKFOJJDGGG {
costed := uint32(0)
if runtime != nil {
costed = runtime.Rules["TbCosted"]
}
return &pb.NJKFOJJDGGG{
UniqueId: cwTrailblazeModifierUID,
FMEKLFFDPGG: &pb.NJKFOJJDGGG_ONKMJAGNECC{
ONKMJAGNECC: &pb.CONKAFGLHFE{
IANFMIJBKAF: costed,
PLAIPMBIHGJ: tbCat.PomThreshold,
},
},
}
}
func cwTrailblazeTraitEffectProto(tbCat *common.CurrencyWarsTrailblazeCatalog, runtime *common.CurrencyWarsRuntimeState) *pb.MGBHKCMHDOK {
return &pb.MGBHKCMHDOK{
EffectId: cwTrailblazeEffect,
NEBNJDMFELO: &pb.MGBHKCMHDOK_KDBKIPPHMFI{
KDBKIPPHMFI: cwTrailblazeShopProto(tbCat, runtime),
},
}
}
type tbListedGood struct {
id uint32
cost uint32
slot int
}
func cwTrailblazeBuy(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, runtime *common.CurrencyWarsRuntimeState, index uint32) (*pb.GridFightSyncUpdateResultScNotify, error) {
if !cwTrailblazeActive(catalog, run, runtime) {
return nil, fmt.Errorf("Trailblaze Silver Rail trait is inactive")
}
layer := cwGetTrailblazeLayer(catalog, run)
tbCat, err := common.LoadCurrencyWarsTrailblaze()
if err != nil {
return nil, err
}
if runtime == nil || runtime.Rules == nil {
return nil, fmt.Errorf("runtime rules missing")
}
if runtime.Rules["TbOpened"] == 0 {
cwTrailblazeNodeEntered(catalog, run, runtime)
}
var listed []tbListedGood
for slot := 0; slot < cwTrailblazeSlots; slot++ {
gid := runtime.Rules[fmt.Sprintf("TbGood/%d", slot)]
if gid != 0 {
if good, ok := tbCat.Goods[gid]; ok {
listed = append(listed, tbListedGood{id: gid, cost: good.Cost, slot: slot})
}
}
}
listed = append(listed, tbListedGood{id: tbCat.RefreshGoodID, cost: 1, slot: -1})
if int(index) >= len(listed) {
return nil, fmt.Errorf("goods index %d out of bounds (listed len %d)", index, len(listed))
}
target := listed[index]
good, ok := tbCat.Goods[target.id]
if !ok {
return nil, fmt.Errorf("goods %d definition missing", target.id)
}
power := runtime.Rules["TbPower"]
if power < target.cost {
return nil, fmt.Errorf("insufficient Trailblaze Power: have %d need %d", power, target.cost)
}
player, team, _ := cwComponents(run)
bag := cwEquipmentBag(run)
if player == nil || team == nil || bag == nil {
return nil, fmt.Errorf("CW components missing")
}
var grantUpdates []*pb.KFNCAIHIHCM
switch good.Kind {
case "role":
if len(good.Params) < 1 {
return nil, fmt.Errorf("role goods %d has empty params", good.ID)
}
roleID := good.Params[0]
star := uint32(1)
if len(good.Params) >= 2 && good.Params[1] > 0 {
star = good.Params[1]
}
front, e := catalog.IntegerRule("GridFight_Front_AvatarMaxNum")
if e != nil {
return nil, e
}
back, e := catalog.IntegerRule("GridFight_Back_AvatarMaxNum")
if e != nil {
return nil, e
}
overflow, e := catalog.IntegerRule("GridFight_Bench_OverFlow_AvatarNum")
if e != nil {
return nil, e
}
occupied := map[uint32]bool{}
for _, r := range team.GridGameRoleList {
occupied[r.Pos] = true
}
var pos uint32
for p := uint64(front+back) + 1; p <= uint64(front+back)+uint64(player.MICEOJBAKGN)+uint64(overflow); p++ {
if !occupied[uint32(p)] {
pos = uint32(p)
break
}
}
if pos == 0 {
return nil, fmt.Errorf("bench is full")
}
uid, e := cwNextEntityID(run)
if e != nil {
return nil, e
}
var scales map[uint32]uint32
if len(team.GridGameRoleList) > 0 {
scales = maps.Clone(team.GridGameRoleList[0].ConvertPropertyToFixpoint)
}
newRole := &pb.CDEAABODKCP{
Id: roleID,
RoleStar: star,
UniqueId: uid,
Pos: pos,
ConvertPropertyToFixpoint: scales,
}
team.GridGameRoleList = append(team.GridGameRoleList, newRole)
grantUpdates = append(grantUpdates, &pb.KFNCAIHIHCM{
MNLCHEJKDHO: &pb.KFNCAIHIHCM_PIDDDHKPDDM{PIDDDHKPDDM: newRole},
})
merged, e := cwMergeRoles(catalog, run, uid)
if e == nil && len(merged) > 0 {
for _, m := range merged {
grantUpdates = append(grantUpdates, m.JKPJFKCBIEM...)
}
}
case "item":
if len(good.Params) < 1 {
return nil, fmt.Errorf("item goods %d has empty params", good.ID)
}
itemID := good.Params[0]
rewards, _ := common.LoadCurrencyWarsRewards(catalog)
isConsumable := false
if rewards != nil {
_, isConsumable = rewards.Consumables[itemID]
}
if isConsumable {
result, err := cwApplyCommand(catalog, run, common.CurrencyWarsCommand{Field: "consumable", Value: itemID, Quantity: 1}, runtime)
if err != nil {
return nil, err
}
for _, g := range result.GCPNIHHBGGD {
grantUpdates = append(grantUpdates, g.JKPJFKCBIEM...)
}
} else {
result, err := cwApplyCommand(catalog, run, common.CurrencyWarsCommand{Field: "equip", Value: itemID, Quantity: 1}, runtime)
if err != nil {
return nil, err
}
for _, g := range result.GCPNIHHBGGD {
grantUpdates = append(grantUpdates, g.JKPJFKCBIEM...)
}
}
case "power":
if len(good.Params) >= 1 {
runtime.Rules["TbPower"] += good.Params[0]
}
case "maze_buff":
if tbBoughtCount(runtime, target.id) > 0 {
return nil, fmt.Errorf("buff %d is already purchased", target.id)
}
case "stat":
// Handled via recorded bought count
}
wasUnlocked := runtime.Rules["TbCosted"] >= tbCat.PomThreshold
runtime.Rules["TbPower"] -= target.cost
runtime.Rules["TbCosted"] += target.cost
if target.id == tbCat.RefreshGoodID {
runtime.Rules["TbRefreshCount"]++
cwTrailblazeRoll(tbCat, run, runtime, tbCat.Tier(layer))
if tbBoughtCount(runtime, tbCat.InitialGoodID) == 0 {
runtime.Rules["TbGood/0"] = tbCat.InitialGoodID
}
} else {
runtime.Rules[fmt.Sprintf("TbBought/%d", target.id)]++
if target.slot >= 0 && target.slot < cwTrailblazeSlots {
runtime.Rules[fmt.Sprintf("TbGood/%d", target.slot)] = 0
}
}
if !wasUnlocked && runtime.Rules["TbCosted"] >= tbCat.PomThreshold {
slotToPut := target.slot
if slotToPut < 0 || slotToPut >= cwTrailblazeSlots || runtime.Rules[fmt.Sprintf("TbGood/%d", slotToPut)] != 0 {
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(catalog, 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),
},
},
},
}
bundle.JKPJFKCBIEM = append(bundle.JKPJFKCBIEM, grantUpdates...)
return &pb.GridFightSyncUpdateResultScNotify{
GCPNIHHBGGD: []*pb.ABPLALCHNFA{bundle},
}, nil
}
func init() {
cwHandle("GridFightTraitUpdate", func(s *Session, req *pb.GridFightTraitUpdateCsReq, rsp *pb.GridFightTraitUpdateScRsp) error {
rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH)
if req == nil {
return nil
}
catalog, err := common.LoadCurrencyWarsCatalog()
if err != nil {
return err
}
data, revision, err := readCurrencyWarsData(catalog)
if err != nil {
return err
}
if data == nil || data.DHFOFLFCLAH == nil {
rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_NOT_IN_GAMEPLAY)
return nil
}
s.currencyWarsRevision = revision
run := data.DHFOFLFCLAH
runtime, err := cwBonfireRuntime(catalog, run)
if err != nil {
return err
}
if req.GetTraitId() == cwTrailblazeTrait && req.GetEffectId() == cwTrailblazeEffect && req.GetKDBKIPPHMFI() != nil {
notify, buyErr := cwTrailblazeBuy(catalog, run, runtime, req.GetKDBKIPPHMFI().Pos)
if buyErr != nil {
PacketLog(fmt.Sprintf("CW_TRAIT_UPDATE Trailblaze rejected: %v", buyErr))
rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_PARAM_NOT_MATCH)
return nil
}
if err = s.saveCurrencyWarsDataWithRuntime(data, catalog, runtime); err != nil {
return err
}
if err = s.Send(notify); err != nil {
return err
}
rsp.Retcode = 0
PacketLog(fmt.Sprintf("CW_TRAIT_UPDATE Trailblaze bought pos=%d power=%d costed=%d", req.GetKDBKIPPHMFI().Pos, runtime.Rules["TbPower"], runtime.Rules["TbCosted"]))
return nil
}
rsp.Retcode = 0
PacketLog(fmt.Sprintf("CW_TRAIT_UPDATE trait=%d effect=%d", req.GetTraitId(), req.GetEffectId()))
return nil
})
}