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

635 lines
22 KiB
Go

package gameserver
import (
"crypto/rand"
"encoding/json"
"fmt"
"math"
"math/big"
"sort"
"strconv"
"strings"
"gac-hkrpg-go/internal/common"
pb "gac-hkrpg-go/protocol/proto"
"google.golang.org/protobuf/proto"
)
const cwPercentScale uint32 = 100 // Uploaded progress is an integer percentage.
type cwNodeTransition struct {
Route uint32 `json:"route_id"`
Chapter uint32 `json:"chapter_id"`
Section uint32 `json:"section_id"`
Stage uint32 `json:"stage_id"`
Penalty uint32 `json:"penalty_id"`
Division uint32 `json:"division_id"`
Overclock bool `json:"is_overclock"`
LevelWire []byte `json:"next_level_wire"`
PendingWire []byte `json:"next_pending_wire"`
RoundWire []byte `json:"round_pending_wire"`
}
func cwNodeTransitions(c *common.CurrencyWarsCatalog) ([]cwNodeTransition, error) {
raw, e := common.ReadResourceFile("resources/CurrencyWarsNodeTransitions.json")
if e != nil {
return nil, e
}
var r struct {
Format uint32 `json:"format_version"`
Client string `json:"target_client_version"`
Assembly string `json:"target_assembly_sha256"`
Entries []cwNodeTransition `json:"entries"`
}
if e = json.Unmarshal(raw, &r); e != nil {
return nil, e
}
if r.Format != 1 || r.Client != c.ClientVersion || r.Assembly != c.Provenance.GameAssemblySHA256 {
return nil, fmt.Errorf("CW node transition revision differs")
}
return r.Entries, nil
}
func cwStage(run *pb.MPIMAEMCEFH) (*pb.BEEFLEJAOCN, *pb.OHMDHKGAEMM) {
if run == nil {
return nil, nil
}
for _, c := range run.NAGDJGMMCBE {
if c == nil {
continue
}
if s := c.GetKLCPAAHDPJF(); s != nil {
for _, n := range s.OOMMIIKPHBC {
if n != nil && n.SectionId == s.SectionId && n.DCAHJELNJNP == s.DCAHJELNJNP {
return s, n.DisplayValue
}
}
return s, nil
}
}
return nil, nil
}
func cwRollNodeOrbs(catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH, routeID, chapter, section, progress uint32) ([]uint32, error) {
route, err := catalog.RouteNodes(routeID)
if err != nil {
return nil, err
}
refNodes, err := common.LoadCurrencyWarsReferenceNodes(catalog)
if err != nil {
return nil, err
}
var template common.CurrencyWarsReferenceNodeTemplate
found := false
for _, r := range route {
if r.ChapterID == chapter && r.SectionID == section {
t, err := refNodes.Template(r.NodeTemplateID)
if err == nil {
template = t
found = true
}
break
}
}
if !found || len(template.ParamList) == 0 {
return nil, nil
}
nodeDropsCat, err := common.LoadCurrencyWarsNodeDrops(catalog)
if err != nil {
return nil, err
}
dropKey := strconv.FormatUint(uint64(template.ParamList[0]), 10)
drop, ok := nodeDropsCat.NodeDrops[dropKey]
if !ok || len(drop.Weights) == 0 {
return nil, nil
}
recipes, err := common.LoadCurrencyWarsOrbRecipes()
if err != nil {
return nil, err
}
supported := map[uint32]bool{}
for _, recipe := range recipes.Recipes {
if recipe.Status == "Implemented" && recipe.Engine == "Orb" {
supported[recipe.ID] = true
}
}
type weightEntry struct {
id uint32
weight uint32
}
var entries []weightEntry
var totalWeight uint32
for k, w := range drop.Weights {
id64, err := strconv.ParseUint(k, 10, 32)
if err != nil {
continue
}
id := uint32(id64)
if supported[id] && w > 0 {
entries = append(entries, weightEntry{id: id, weight: w})
totalWeight += w
}
}
if totalWeight == 0 {
return nil, nil
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].id < entries[j].id
})
percent := progress
if percent > 100 {
percent = 100
}
count := (drop.Rolls*percent + 99) / 100
var rolled []uint32
for i := uint32(0); i < count; i++ {
rBig, err := rand.Int(rand.Reader, big.NewInt(int64(totalWeight)))
if err != nil {
return nil, err
}
roll := uint32(rBig.Int64())
for _, e := range entries {
if roll < e.weight {
rolled = append(rolled, e.id)
break
}
roll -= e.weight
}
}
return rolled, nil
}
func finishCurrencyWarsBattle(s *Session, req *pb.PVEBattleResultCsReq, current *pb.SceneBattleInfo) error {
if current == nil || req.BattleId != current.BattleId || req.StageId != current.StageId {
return fmt.Errorf("CW battle identity differs")
}
if req.EndStatus != pb.BattleEndStatus_BATTLE_END_WIN {
// Quitting returns to the same preparation; it does not award a victory.
CURRENT_BATTLE.Delete(s.Token)
return s.Send(cwNotify(cwUnlock()))
}
if req.Stt == nil || req.Stt.APAGMJNECCN == nil {
return fmt.Errorf("CW uploaded statistics missing")
}
progress := req.Stt.APAGMJNECCN.HFLPDCJDIAC
if progress > cwPercentScale {
return fmt.Errorf("CW progress is outside percentage range")
}
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 || revision != s.currencyWarsRevision {
return fmt.Errorf("CW run/revision differs at settlement")
}
run := data.DHFOFLFCLAH
stage, node := cwStage(run)
player, team, shop := cwComponents(run)
if stage == nil || node == nil || player == nil || team == nil || shop == nil || node.StageId != req.StageId {
return fmt.Errorf("CW settlement components differ")
}
// Encounter penalties belong to the selected offer, not the route node's
// base display rule. Validate against the battle we actually issued before
// using that rule for HP/rewards or selecting the next transition.
penaltyID := node.IFIIOIIKJHO
var selectedBranch *pb.KKBGIPCAGGH
if layer := stage.GetIMPNAHBCADN().GetMPOBBIPMNNJ(); layer != nil && len(layer.BPDABHGFBFO) > 0 {
selected, e := cwSelectedEnemyBranch(layer)
if e != nil {
return e
}
penaltyID = selected.IFIIOIIKJHO
selectedBranch = selected
if current.GetEIGMKOJOEEI().GetIFIIOIIKJHO() != penaltyID {
return fmt.Errorf("CW selected Encounter penalty differs from issued battle")
}
}
transitions, err := cwNodeTransitions(catalog)
if err != nil {
return err
}
var transition *cwNodeTransition
for i := range transitions {
t := &transitions[i]
if t.Route == stage.EMOHLJHPOPC && t.Chapter == stage.DCAHJELNJNP && t.Section == stage.SectionId && t.Stage == node.StageId && t.Penalty == penaltyID && t.Division == run.DivisionId && t.Overclock == run.IsOverlock {
if transition != nil {
return fmt.Errorf("CW transition duplicated")
}
transition = t
}
}
nextLevel := new(pb.AINFNFIDGFF)
nextPending := new(pb.CHCHAJIANIM)
if transition != nil {
if err = proto.Unmarshal(transition.LevelWire, nextLevel); err != nil {
return err
}
if err = proto.Unmarshal(transition.PendingWire, nextPending); err != nil {
return err
}
if nextLevel.IMPNAHBCADN == nil || nextPending.GetNNDGEJFLGGF() == nil {
return fmt.Errorf("CW next node recipe incomplete")
}
found := false
for _, n := range stage.OOMMIIKPHBC {
found = found || (n.DCAHJELNJNP == nextLevel.DCAHJELNJNP && n.SectionId == nextLevel.SectionId)
}
if !found {
return fmt.Errorf("CW next node outside route")
}
}
raw, err := common.ReadResourceFile("resources/CurrencyWarsSettlementRules.json")
if err != nil {
return err
}
var rules struct {
Format uint32 `json:"format_version"`
Client string `json:"target_client_version"`
Assembly string `json:"target_assembly_sha256"`
Penalties []struct{ ID, ThresholdPosition, ThresholdFailPlayerHPPenalty, ThresholdPassBasicPlayerHPPenalty, ProgressPenaltyCoefficient uint32 } `json:"penalty_rules"`
Victories []struct{ Low, Up, Bonus uint32 } `json:"victory_rules"`
StreakNodes []string `json:"streak_node_types"`
}
if err = json.Unmarshal(raw, &rules); err != nil {
return err
}
if rules.Format != 1 || rules.Client != catalog.ClientVersion || rules.Assembly != catalog.Provenance.GameAssemblySHA256 {
return fmt.Errorf("CW settlement rule revision differs")
}
penaltyIndex := -1
for i, p := range rules.Penalties {
if p.ID == penaltyID {
if penaltyIndex >= 0 {
return fmt.Errorf("CW penalty duplicated")
}
penaltyIndex = i
}
}
var penalty common.CurrencyWarsReferencePenalty
if penaltyIndex >= 0 {
p := rules.Penalties[penaltyIndex]
penalty = common.CurrencyWarsReferencePenalty{ID: p.ID, ThresholdPosition: p.ThresholdPosition, ThresholdFailPlayerHPPenalty: p.ThresholdFailPlayerHPPenalty, ThresholdPassBasicPlayerHPPenalty: p.ThresholdPassBasicPlayerHPPenalty, ProgressPenaltyCoefficient: p.ProgressPenaltyCoefficient}
} else {
// Preserve the proven current-Client captures above. Newly constructed
// nodes use the separately labelled, revision-gated reference table.
reference, e := common.LoadCurrencyWarsReferencePenalties(catalog)
if e != nil {
return e
}
var ok bool
penalty, ok = reference[penaltyID]
if !ok {
return fmt.Errorf("CW target penalty %d has no reference row", penaltyID)
}
}
if player.PPKKHHNEOHB > math.MaxInt32 || player.GridFightLineupHp > player.PPKKHHNEOHB {
return fmt.Errorf("CW HP cannot be represented in settlement")
}
perfect := progress == cwPercentScale
previousHP := player.GridFightLineupHp
previousStreak := player.GDAKCOICIDA
previousLevel, previousExp := player.EHNJDIDABHD, player.GridFightCurLevelExp
var hpChanges []*pb.GEDDNPGHNGP
loseHP := func(amount uint32, reason pb.DFIJIBKBEJL) {
before := player.GridFightLineupHp
if amount > before {
amount = before
}
player.GridFightLineupHp -= amount
hpChanges = append(hpChanges, &pb.GEDDNPGHNGP{Reason: reason, ODJEBDNBLLC: int32(before), BMMADDOCJOG: int32(player.GridFightLineupHp), IOAOHNDEJNB: amount})
}
basicLoss, progressLoss, thresholdLoss := uint32(0), uint32(0), uint32(0)
if !perfect {
basicLoss = penalty.ThresholdPassBasicPlayerHPPenalty
loss := uint64(cwPercentScale-progress) * uint64(penalty.ProgressPenaltyCoefficient) / uint64(cwPercentScale)
if loss > math.MaxUint32 {
return fmt.Errorf("CW progress penalty overflow")
}
progressLoss = uint32(loss)
if progress < penalty.ThresholdPosition {
thresholdLoss = penalty.ThresholdFailPlayerHPPenalty
}
}
loseHP(basicLoss, pb.DFIJIBKBEJL_DFIJIBKBEJL_FDKCPHMMFCD)
loseHP(progressLoss, pb.DFIJIBKBEJL_DFIJIBKBEJL_IEGEHAKDEHA)
loseHP(thresholdLoss, pb.DFIJIBKBEJL_DFIJIBKBEJL_KEOPICGMGPI)
isDefeat := (player.GridFightLineupHp == 0)
kind, err := catalog.NodeTypeName(node.CJCCMAACJDB)
if err != nil {
return err
}
if isDefeat {
player.GDAKCOICIDA = 0
} else {
for _, name := range rules.StreakNodes {
if name == kind {
if perfect {
if player.GDAKCOICIDA == math.MaxUint32 {
return fmt.Errorf("CW streak overflow")
}
player.GDAKCOICIDA++
} else {
player.GDAKCOICIDA = 0
}
break
}
}
}
var streakGold uint32
if !isDefeat {
bonusFound := false
for _, v := range rules.Victories {
if v.Low <= player.GDAKCOICIDA && player.GDAKCOICIDA <= v.Up {
if bonusFound {
return fmt.Errorf("CW victory ranges overlap")
}
bonusFound = true
streakGold = v.Bonus
}
}
if !bonusFound {
return fmt.Errorf("CW victory range missing")
}
}
interest, err := catalog.BaseInterest(player.Gold, player.KNFOCCPOJEA)
if err != nil {
return err
}
basicGold := node.PICMDONCKCO
if isDefeat {
basicGold = 0
streakGold = 0
}
sum := uint64(player.Gold) + uint64(basicGold) + uint64(interest) + uint64(streakGold)
if sum > math.MaxUint32 {
sum = math.MaxUint32
}
player.Gold = uint32(sum)
var gain uint32
if !isDefeat {
g, err := catalog.BattleNodeExperience(run.IsOverlock, stage.DCAHJELNJNP, kind)
if err != nil {
return err
}
gain = uint32(g)
}
plan, err := catalog.PlanBaseExperience(player.EHNJDIDABHD, player.GridFightCurLevelExp, gain, player.NDAINFLOLPJ)
if err != nil {
return err
}
previousRow, err := catalog.PlayerLevel(previousLevel)
if err != nil {
return err
}
row, err := catalog.PlayerLevel(plan.Level)
if err != nil {
return err
}
if gain > 0 {
if err = cwApplyBaseExperience(catalog, player, shop, gain); err != nil {
return err
}
}
var encounterUpdates []*pb.KFNCAIHIHCM
runtime, err := cwBonfireRuntime(catalog, run)
if err != nil {
return err
}
if kind == "EliteBranch" && !isDefeat {
encounterUpdates, err = cwEncounterRewards(catalog, run, selectedBranch, progress, penalty.ThresholdPosition, runtime)
if err != nil {
return err
}
}
damage := &pb.BHBAILHMCOI{}
frontMax, err := catalog.IntegerRule("GridFight_Front_AvatarMaxNum")
if err != nil {
return err
}
for _, role := range current.EIGMKOJOEEI.GridGameRoleList {
total := float64(0)
if req.Stt != nil && req.Stt.APAGMJNECCN != nil {
for _, stat := range req.Stt.APAGMJNECCN.DOGMMGLBJIJ {
if (stat.RoleId == role.RoleId || stat.RoleId == role.AvatarId || stat.RoleId == role.UniqueId) && !math.IsNaN(stat.Damage) && !math.IsInf(stat.Damage, 0) && stat.Damage > 0 {
total += stat.Damage
}
}
}
if total == 0 && req.Stt != nil {
for _, ba := range req.Stt.BattleAvatarList {
if (ba.Id == role.AvatarId || ba.Id == role.RoleId) && !math.IsNaN(ba.TotalDamage) && !math.IsInf(ba.TotalDamage, 0) && ba.TotalDamage > 0 {
total += ba.TotalDamage
}
}
}
damage.EEGPILJDLBG = append(damage.EEGPILJDLBG, &pb.PLFKMENCDMM{RoleId: role.RoleId, RoleStar: role.RoleStar, TotalDamage: total, DIGNECHKINI: uint64(role.Pos) > uint64(frontMax)})
}
oldChapter, oldSection := stage.DCAHJELNJNP, stage.SectionId
var nextNotify *pb.GridFightSyncUpdateResultScNotify
isFinalNode := false
if isDefeat {
// Terminal defeat: stage does not advance
} else if transition != nil {
stage.DCAHJELNJNP = nextLevel.DCAHJELNJNP
stage.SectionId = nextLevel.SectionId
stage.IMPNAHBCADN = proto.Clone(nextLevel.IMPNAHBCADN).(*pb.NCKOKDHNJDC)
run.FMIGOKIDKJL = nextPending
nextNotify = cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_OOICHLAMNIG{OOICHLAMNIG: nextLevel}}, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: nextPending}})
if stage.ANJBLBEOJND != nil && stage.ANJBLBEOJND.PIFNOKAMBLL != nil && stage.ANJBLBEOJND.PIFNOKAMBLL[cwDifficultySourceBranch] != 0 {
diffUpdate, err := cwXaodevDifficulty(catalog, run, 0)
if err == nil && diffUpdate != nil {
nextNotify.GCPNIHHBGGD[0].JKPJFKCBIEM = append(nextNotify.GCPNIHHBGGD[0].JKPJFKCBIEM, diffUpdate)
}
}
} else {
// A battle has no pending action to allocate from. The persisted CAS
// revision is monotonic and also backs Bonfire entry queue allocation.
if revision >= math.MaxUint32 {
return fmt.Errorf("CW settlement queue exhausted")
}
run.FMIGOKIDKJL = &pb.CHCHAJIANIM{QueuePosition: uint32(revision)}
nextNotify, err = cwAdvanceRouteNode(catalog, run, runtime)
if err != nil {
if strings.Contains(err.Error(), "no next node") {
isFinalNode = true
} else {
return err
}
} else {
// The allocation seed is not a Client pending action, so it must not
// produce a synthetic FinishPendingAction notification.
for _, group := range nextNotify.GCPNIHHBGGD {
kept := group.JKPJFKCBIEM[:0]
for _, update := range group.JKPJFKCBIEM {
if update.GetPMFBIGLPJJA() != uint32(revision) {
kept = append(kept, update)
}
}
group.JKPJFKCBIEM = kept
}
}
}
if !isDefeat && !isFinalNode {
runtime, err = cwApplyBonfireEnemyRules(catalog, run, runtime)
if err != nil {
return err
}
if runtime.Rules["PetPalStarDown"] != 0 && stage.GetIMPNAHBCADN().GetMPOBBIPMNNJ() != nil {
nextNotify.GCPNIHHBGGD = append(nextNotify.GCPNIHHBGGD, cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_OOICHLAMNIG{OOICHLAMNIG: &pb.AINFNFIDGFF{DCAHJELNJNP: stage.DCAHJELNJNP, SectionId: stage.SectionId, IMPNAHBCADN: stage.IMPNAHBCADN}}}).GCPNIHHBGGD...)
}
}
tbLayer := cwGetTrailblazeLayer(catalog, run)
if tbLayer > 0 {
if tbCat, err := common.LoadCurrencyWarsTrailblaze(); err == nil {
income := tbCat.Income(tbLayer)
runtime.Rules["TbPower"] += income
PacketLog(fmt.Sprintf("CW_TRAILBLAZE_INCOME layer=%d income=%d total_power=%d", tbLayer, income, runtime.Rules["TbPower"]))
}
}
cwSyncTraitComponent(catalog, run, runtime)
var droppedOrbs []*pb.IBPDPCOGDEP
var orbUpdates []*pb.KFNCAIHIHCM
if !isDefeat {
orbIDs, dropErr := cwRollNodeOrbs(catalog, run, stage.EMOHLJHPOPC, oldChapter, oldSection, progress)
if dropErr != nil {
PacketLog(fmt.Sprintf("CW_DROP_ORBS error: %v", dropErr))
} else if len(orbIDs) > 0 {
var orbBag *pb.DBONCGFHHDN
for _, c := range run.NAGDJGMMCBE {
if b := c.GetDBAGKJNPDOL(); b != nil {
orbBag = b
break
}
}
if orbBag == nil {
orbBag = &pb.DBONCGFHHDN{}
run.NAGDJGMMCBE = append(run.NAGDJGMMCBE, &pb.GridFightGameInfo{
LEOIAOFBBFA: &pb.GridFightGameInfo_DBAGKJNPDOL{
DBAGKJNPDOL: orbBag,
},
})
}
for _, orbID := range orbIDs {
uid, idErr := cwNextEntityID(run)
if idErr != nil {
break
}
orb := &pb.IBPDPCOGDEP{
UniqueId: uid,
LDBNOMPCOKK: orbID,
}
orbBag.MDKGCIKOMLI = append(orbBag.MDKGCIKOMLI, orb)
droppedOrbs = append(droppedOrbs, orb)
orbUpdates = append(orbUpdates, &pb.KFNCAIHIHCM{
MNLCHEJKDHO: &pb.KFNCAIHIHCM_AFILIPBHMOO{
AFILIPBHMOO: &pb.MMEDLCAENAL{
UniqueId: uid,
LDBNOMPCOKK: orbID,
},
},
})
}
PacketLog(fmt.Sprintf("CW_DROP_ORBS node=%d-%d dropped=%d", oldChapter, oldSection, len(droppedOrbs)))
}
}
player.GAJGBBFELEL = &pb.GMBMNFLMKPI{IBCPAKJAPFF: pb.KMJNPEMPNMC_KMJNPEMPNMC_AEINKINPAFL, LMICDNNABNM: pb.GGNKDLAHAGH_GGNKDLAHAGH_FHOHPFBDNOH}
end := &pb.GridFightEndBattleStageNotify{DCAHJELNJNP: oldChapter, SectionId: oldSection, EMOHLJHPOPC: stage.EMOHLJHPOPC, HFLPDCJDIAC: progress, OAHFCPPIMLF: penalty.ThresholdPosition, CBHJKFNCDAD: perfect,
JPKHBELGHCG: previousStreak, OCCOHKIMGCN: player.GDAKCOICIDA, CLFNPMGHMFC: basicGold, DMNILJEGICO: interest, OGLOCDMNNIM: streakGold, ODJEBDNBLLC: previousHP, PAGPKOKMDBK: player.GridFightLineupHp, LKCJAPIGEND: player.PPKKHHNEOHB, NKBIDLBLONC: damage, HMKOKACNHJH: hpChanges,
AEOCOGHMEBG: &pb.GEEMFDBDMDH{PBMAHOJBAGG: uint32(gain), EDCLIPCEHAO: &pb.NFPLDMFCLHA{Level: previousLevel, Exp: previousExp, KDBMPKJKAKI: previousRow.LevelUpExp}, LHIAODNJIIC: &pb.NFPLDMFCLHA{Level: plan.Level, Exp: plan.Exp, KDBMPKJKAKI: row.LevelUpExp}}}
if len(droppedOrbs) > 0 {
var dropEntries []*pb.BCCONKJHDJL
for _, orb := range droppedOrbs {
dropEntries = append(dropEntries, &pb.BCCONKJHDJL{
FKKGLFOHGGA: pb.MJJIFGINJAJ_MJJIFGINJAJ_ANMDMBGNOHL,
FNLMKJLKLFH: orb.LDBNOMPCOKK,
Num: 1,
})
}
end.AFFFCONCDMG = map[uint32]*pb.GridFightDropInfo{
0: {NMGIABHIJJB: dropEntries},
}
}
update := cwNotify(cwUnlock(), cwGoldSync(player.Gold), cwCapacitySync(player), &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_GridFightLineupHp{GridFightLineupHp: &pb.FMJDDIHBELC{PPKKHHNEOHB: player.PPKKHHNEOHB, GridFightLineupHp: player.GridFightLineupHp}}}, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_PLPHEIBLPDL{PLPHEIBLPDL: &pb.PIHKLPJGEPM{Level: plan.Level, Exp: plan.Exp, HFMKLAPEKFA: player.NDAINFLOLPJ}}})
update.GCPNIHHBGGD[0].JKPJFKCBIEM = append(update.GCPNIHHBGGD[0].JKPJFKCBIEM, encounterUpdates...)
update.GCPNIHHBGGD[0].JKPJFKCBIEM = append(update.GCPNIHHBGGD[0].JKPJFKCBIEM, orbUpdates...)
if tbCat, err := common.LoadCurrencyWarsTrailblaze(); err == nil && (tbLayer > 0 || runtime.Rules["TbOpened"] != 0) {
update.GCPNIHHBGGD[0].JKPJFKCBIEM = append(update.GCPNIHHBGGD[0].JKPJFKCBIEM,
&pb.KFNCAIHIHCM{
MNLCHEJKDHO: &pb.KFNCAIHIHCM_HKHJJCMKKJE{
HKHJJCMKKJE: &pb.JLKCBOIFJKC{
TraitId: cwTrailblazeTrait,
ENPLACDLJGA: cwTrailblazeTraitEffectProto(tbCat, runtime),
},
},
},
&pb.KFNCAIHIHCM{
MNLCHEJKDHO: &pb.KFNCAIHIHCM_GOOCHGKBIPN{
GOOCHGKBIPN: cwTrailblazeModifierProto(tbCat, runtime),
},
},
)
}
if isDefeat || isFinalNode {
reason := pb.PEIJNEFOCHI_PEIJNEFOCHI_JOJPGBCECLN
if isDefeat {
reason = pb.PEIJNEFOCHI_PEIJNEFOCHI_HIGMFLKHGNJ
}
keepWin, settle, termErr := currencyWarsTerminalSettlement(catalog, data, run, reason, !isDefeat, damage.EEGPILJDLBG)
if termErr != nil {
return termErr
}
data.DHFOFLFCLAH = nil
if termErr = s.saveCurrencyWarsData(data, catalog); termErr != nil {
return termErr
}
CURRENT_BATTLE.Delete(s.Token)
s.currencyWarsOpen = false
if err = s.Send(update); err != nil {
return err
}
if err = s.Send(end); err != nil {
return err
}
if err = cwSend(s, keepWin); err != nil {
return err
}
if err = cwSend(s, settle); err != nil {
return err
}
PacketLog(fmt.Sprintf("CW_SETTLEMENT terminal victory=%v battle=%d progress=%d gold=%d node=%d-%d", !isDefeat, req.BattleId, progress, player.Gold, oldChapter, oldSection))
return nil
}
if err = s.saveCurrencyWarsDataWithRuntime(data, catalog, runtime); err != nil {
return err
}
CURRENT_BATTLE.Delete(s.Token) // Duplicate uploads cannot award again.
if err = s.Send(update); err != nil {
return err
}
if err = s.Send(end); err != nil {
return err
}
nextNotify.GCPNIHHBGGD = append(nextNotify.GCPNIHHBGGD, cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_JOMFJHOCHJP{JOMFJHOCHJP: player.GAJGBBFELEL}}).GCPNIHHBGGD...)
if err = s.Send(nextNotify); err != nil {
return err
}
PacketLog(fmt.Sprintf("CW_SETTLEMENT saved battle=%d progress=%d gold=%d basic=%d interest=%d streak=%d exp=%d next=%d/%d revision=%d", req.BattleId, progress, player.Gold, basicGold, interest, streakGold, plan.Exp, stage.DCAHJELNJNP, stage.SectionId, s.currencyWarsRevision))
return nil
}