229 lines
8.7 KiB
Go
229 lines
8.7 KiB
Go
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
|
|
})
|
|
}
|