package gameserver import ( "encoding/json" "fmt" "maps" "sort" "gac-hkrpg-go/internal/common" pb "gac-hkrpg-go/protocol/proto" "google.golang.org/protobuf/proto" ) // The captured first-node response supplies the mapped CW envelope and role // mode schema. Other supported nodes replace its enemy recipe from their own // captured parameters and the live saved enemy layer before party construction. func buildCurrencyWarsBattle(s *Session, catalog *common.CurrencyWarsCatalog, run *pb.MPIMAEMCEFH) (*pb.SceneBattleInfo, error) { raw, err := common.ReadResourceFile("resources/CurrencyWarsBattleTemplates.json") if err != nil { return nil, err } var recipe struct { Format uint32 `json:"format_version"` Client string `json:"target_client_version"` Assembly string `json:"target_assembly_sha256"` Wire []byte `json:"battle_wire"` } if err = json.Unmarshal(raw, &recipe); err != nil { return nil, err } if recipe.Format != 1 || recipe.Client != catalog.ClientVersion || recipe.Assembly != catalog.Provenance.GameAssemblySHA256 { return nil, fmt.Errorf("CW battle recipe revision differs") } info := new(pb.SceneBattleInfo) if err = proto.Unmarshal(recipe.Wire, info); err != nil { return nil, err } grid := info.EIGMKOJOEEI player, team, _ := cwComponents(run) if player == nil || team == nil || grid == nil || grid.IOCKDJCDMAH == nil || len(grid.GridGameRoleList) == 0 { return nil, fmt.Errorf("CW battle components missing") } var stage *pb.BEEFLEJAOCN for _, c := range run.NAGDJGMMCBE { if v := c.GetKLCPAAHDPJF(); v != nil { stage = v } if v := c.GetKFBIEANENKJ(); v != nil { grid.StageInfo = &pb.KJFEHJMAIAA{FMNPBOJGLFI: v.FMNPBOJGLFI} } } if stage == nil || run.DivisionId != grid.AACGBEBGDPE || run.IsOverlock != grid.IsOverlock { return nil, fmt.Errorf("CW battle envelope has not been captured for this mode/division") } var node *pb.OHMDHKGAEMM for _, n := range stage.OOMMIIKPHBC { if n.SectionId == stage.SectionId && n.DCAHJELNJNP == stage.DCAHJELNJNP { node = n.DisplayValue } } if node == nil { return nil, fmt.Errorf("CW current node missing") } if stage.EMOHLJHPOPC == grid.IOCKDJCDMAH.EMOHLJHPOPC && stage.DCAHJELNJNP == grid.IOCKDJCDMAH.DCAHJELNJNP && stage.SectionId == grid.IOCKDJCDMAH.SectionId { if node.StageId != info.StageId || node.IFIIOIIKJHO != grid.IFIIOIIKJHO { return nil, fmt.Errorf("CW first enemy recipe stage/penalty differs") } } else if err = cwBuildNodeEnemy(catalog, run, stage, node, info); err != nil { return nil, err } front, err := catalog.IntegerRule("GridFight_Front_AvatarMaxNum") if err != nil { return nil, err } back, err := catalog.IntegerRule("GridFight_Back_AvatarMaxNum") if err != nil { return nil, err } ext := proto.Clone(grid.GridGameRoleList[0].DCEOLAAMGOO).(*pb.JJHJCIAPADP) // This is the native mode envelope: tag 1 is the captured CW mode, while // tag 2 is SpotlightID. Keep the mode, remove only the reference binding. if ext.GLHDCCPOGHL == nil { return nil, fmt.Errorf("CW battle mode envelope missing") } ext.GLHDCCPOGHL.IHHNLLAJAGB = 0 // These fields contain the reference party's traits, buffs and battle events. // They cannot be reused for another party. Derived trait/equipment effects // remain a separate implementation task; don't silently inherit its bonuses. grid.GridGameRoleList = nil grid.GridGameNpcList = nil grid.OPHONCKPNMG = nil grid.GridFightTraitInfo = nil grid.POCKGKDFLKK = nil grid.KCOMPKMJCMC = nil grid.SyncAugmentInfo = nil for _, component := range run.NAGDJGMMCBE { if owned := component.GetLLDBOIANGDN(); owned != nil { for _, a := range owned.SyncAugmentInfo { grid.SyncAugmentInfo = append(grid.SyncAugmentInfo, &pb.BGLCGNCLHLJ{AugmentId: a.AugmentId}) } } } info.BattleAvatarList = nil info.BuffList = nil info.BattleEvent = nil supported, err := supportedRelicIDs() if err != nil { return nil, err } adventure := s.JSON() if adventure == nil { return nil, fmt.Errorf("CW avatar data not loaded") } roles := append([]*pb.CDEAABODKCP(nil), team.GridGameRoleList...) sort.Slice(roles, func(i, j int) bool { return roles[i].Pos < roles[j].Pos }) seen := map[uint32]bool{} for _, role := range roles { if role.Pos == 0 || uint64(role.Pos) > uint64(front+back) { continue } config, err := catalog.CapturedRoleVariant(role.Id, role.RoleStar) if err != nil { return nil, err } avatarID := config.Config.AvatarID if seen[avatarID] { return nil, fmt.Errorf("CW duplicate active avatar %d", avatarID) } seen[avatarID] = true avatar := adventure.Avatars[avatarID] var battleAvatar *pb.BattleAvatar if avatar == nil || config.Config.IsFakeAvatar != 0 { battleAvatar, err = cwTrialBattleAvatar(catalog, config, uint32(len(info.BattleAvatarList))) if err != nil { return nil, err } } else { battleAvatar, _ = avatar.ToBattleAvatarProto(uint32(len(info.BattleAvatarList)), adventure.FindLightconeFor(avatarID), filterSupportedRelics(adventure.FindRelicsFor(avatarID), supported)) battleAvatar.AvatarType = pb.AvatarType_AvatarType_AvatarGridFightType } if uint64(role.Pos) <= uint64(front) { info.BattleAvatarList = append(info.BattleAvatarList, battleAvatar) } else { battleAvatar.Index = uint32(len(grid.OPHONCKPNMG)) grid.OPHONCKPNMG = append(grid.OPHONCKPNMG, battleAvatar) } roleExt := proto.Clone(ext).(*pb.JJHJCIAPADP) if spotlights := cwSpotlights(run); spotlights != nil { for _, binding := range spotlights.FNNEPMAPOOF { if binding.RoleId == role.Id && binding.MDNAGANGNKK == role.UniqueId && !binding.ELBPNDIENDL { roleExt.GLHDCCPOGHL.IHHNLLAJAGB = binding.IHHNLLAJAGB } } } battleRole := &pb.BJGJBNJDLIK{RoleId: role.Id, RoleStar: role.RoleStar, Pos: role.Pos, AvatarId: avatarID, UniqueId: role.UniqueId, ConvertPropertyToFixpoint: maps.Clone(role.ConvertPropertyToFixpoint), DCEOLAAMGOO: roleExt, BABNHPDGNGB: maps.Clone(role.BABNHPDGNGB)} bag := cwEquipmentBag(run) for _, uid := range role.PNJIPHJJONG { var gear *pb.IJDLENJJBHJ if bag != nil { for _, owned := range bag.GridFightEquipmentList { if owned.UniqueId == uid { gear = owned break } } } if gear == nil { return nil, fmt.Errorf("CW role equipment %d is missing from saved inventory", uid) } battleRole.CNIJIJMEPEF = append(battleRole.CNIJIJMEPEF, &pb.ACFLKPFDHIM{UniqueId: gear.UniqueId, GridFightEquipmentId: gear.GridFightEquipmentId, ConvertPropertyToFixpoint: maps.Clone(gear.ConvertPropertyToFixpoint)}) } grid.GridGameRoleList = append(grid.GridGameRoleList, battleRole) } if len(info.BattleAvatarList) == 0 || uint32(len(seen)) > player.AFFCFLFIFPH { return nil, fmt.Errorf("CW active party is empty or exceeds capacity") } grid.GridFightLineupHp = player.GridFightLineupHp grid.EHNJDIDABHD = player.AFFCFLFIFPH grid.HHEPCDMBJFI = uint32(len(seen)) grid.LJNGPCKPJLF = run.LJNGPCKPJLF grid.GridFightPortalBuffList = nil for _, portal := range stage.GridFightPortalBuffList { grid.GridFightPortalBuffList = append(grid.GridFightPortalBuffList, &pb.KHFHMJEMBOF{PortalBuffId: portal.PortalBuffId}) } // NPCs carry run-owned equipment identities, independent of the copied enemy recipe. for _, npc := range team.GridGameNpcList { value := &pb.PKDNDNABKII{NpcId: npc.Id, UniqueId: npc.UniqueId, Pos: npc.Pos} for _, id := range npc.PNJIPHJJONG { for _, c := range run.NAGDJGMMCBE { if bag := c.GetGCKLIONKMJC(); bag != nil { for _, eq := range bag.GridFightEquipmentList { if eq.UniqueId == id { value.GridFightEquipmentList = append(value.GridFightEquipmentList, &pb.ACFLKPFDHIM{UniqueId: id, GridFightEquipmentId: eq.GridFightEquipmentId}) } } } } } grid.GridGameNpcList = append(grid.GridGameNpcList, value) } info.LogicRandomSeed = randUint32() if s.currencyWarsRevision >= uint64(^uint32(0)) { return nil, fmt.Errorf("CW battle identifier exhausted") } info.BattleId = uint32(s.currencyWarsRevision + 1) return info, nil } func init() { cwHandle("GridFightEnterBattleStage", func(s *Session, _ *pb.GridFightEnterBattleStageCsReq, rsp *pb.GridFightEnterBattleStageScRsp) error { rsp.Retcode = uint32(pb.Retcode_RET_GRID_FIGHT_CONF_MISS) catalog, data, err := cwPreparingData(s) if err != nil { PacketLog("CW_ENTER_BATTLE rejected: " + err.Error()) return nil } info, err := buildCurrencyWarsBattle(s, catalog, data.DHFOFLFCLAH) if err != nil { PacketLog("CW_ENTER_BATTLE rejected: " + err.Error()) return nil } CURRENT_BATTLE.Store(s.Token, info) rsp.BattleInfo = info rsp.Retcode = 0 PacketLog(fmt.Sprintf("CW_ENTER_BATTLE stage=%d roles=%d background=%d battle=%d", info.StageId, len(info.BattleAvatarList), len(info.EIGMKOJOEEI.OPHONCKPNMG), info.BattleId)) return nil }) }