pre cw with probe and a bunch script to read game behavior
This commit is contained in:
1 parent
5120219ae2
commit
da8a32f60d
310 files changed
+219693
-872
No files matched your search
@@ -0,0 +1,293 @@
|
||||
package gameserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"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) {
|
||||
for _, c := range run.NAGDJGMMCBE {
|
||||
if s := c.GetKLCPAAHDPJF(); s != nil {
|
||||
for _, n := range s.OOMMIIKPHBC {
|
||||
if n.SectionId == s.SectionId && n.DCAHJELNJNP == s.DCAHJELNJNP {
|
||||
return s, n.DisplayValue
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
return nil, 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")
|
||||
}
|
||||
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 == node.IFIIOIIKJHO && t.Division == run.DivisionId && t.Overclock == run.IsOverlock {
|
||||
if transition != nil {
|
||||
return fmt.Errorf("CW transition duplicated")
|
||||
}
|
||||
transition = t
|
||||
}
|
||||
}
|
||||
if transition == nil {
|
||||
return fmt.Errorf("CW next node recipe not captured")
|
||||
}
|
||||
nextLevel := new(pb.AINFNFIDGFF)
|
||||
nextPending := new(pb.CHCHAJIANIM)
|
||||
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 == node.IFIIOIIKJHO {
|
||||
if penaltyIndex >= 0 {
|
||||
return fmt.Errorf("CW penalty duplicated")
|
||||
}
|
||||
penaltyIndex = i
|
||||
}
|
||||
}
|
||||
if penaltyIndex < 0 {
|
||||
return fmt.Errorf("CW target penalty row not captured")
|
||||
}
|
||||
penalty := rules.Penalties[penaltyIndex]
|
||||
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)
|
||||
if player.GridFightLineupHp == 0 {
|
||||
return fmt.Errorf("CW defeat settlement requires terminal-run handler")
|
||||
}
|
||||
kind, err := catalog.NodeTypeName(node.CJCCMAACJDB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
bonusFound := false
|
||||
var streakGold uint32
|
||||
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
|
||||
sum := uint64(player.Gold) + uint64(basicGold) + uint64(interest) + uint64(streakGold)
|
||||
if sum > math.MaxUint32 {
|
||||
sum = math.MaxUint32
|
||||
}
|
||||
player.Gold = uint32(sum)
|
||||
gain, err := catalog.IntegerRule("GridFight_GainExpWhenWaveEnd")
|
||||
if err != nil || gain < 0 || uint64(gain) > math.MaxUint32 {
|
||||
return fmt.Errorf("CW EXP gain rule invalid")
|
||||
}
|
||||
plan, err := catalog.PlanBaseExperience(player.EHNJDIDABHD, player.GridFightCurLevelExp, uint32(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 err = cwApplyBaseExperience(catalog, player, shop, uint32(gain)); 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)
|
||||
for _, stat := range req.Stt.APAGMJNECCN.DOGMMGLBJIJ {
|
||||
if stat.RoleId == role.RoleId && !math.IsNaN(stat.Damage) && !math.IsInf(stat.Damage, 0) && stat.Damage > 0 {
|
||||
total += stat.Damage
|
||||
}
|
||||
}
|
||||
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
|
||||
stage.DCAHJELNJNP = nextLevel.DCAHJELNJNP
|
||||
stage.SectionId = nextLevel.SectionId
|
||||
stage.IMPNAHBCADN = proto.Clone(nextLevel.IMPNAHBCADN).(*pb.NCKOKDHNJDC)
|
||||
run.FMIGOKIDKJL = nextPending
|
||||
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 err = s.saveCurrencyWarsData(data, catalog); err != nil {
|
||||
return err
|
||||
}
|
||||
CURRENT_BATTLE.Delete(s.Token) // Duplicate uploads cannot award again.
|
||||
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}}})
|
||||
if err = s.Send(update); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = s.Send(end); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = s.Send(cwNotify(&pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_OOICHLAMNIG{OOICHLAMNIG: nextLevel}}, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_JOMFJHOCHJP{JOMFJHOCHJP: player.GAJGBBFELEL}}, &pb.KFNCAIHIHCM{MNLCHEJKDHO: &pb.KFNCAIHIHCM_FMIGOKIDKJL{FMIGOKIDKJL: nextPending}})); 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
|
||||
}
|
||||
Reference in new issue
Block a user