diff --git a/logic/controller/fight_base.go b/logic/controller/fight_base.go index 6d793bf7d..6c611640c 100644 --- a/logic/controller/fight_base.go +++ b/logic/controller/fight_base.go @@ -43,7 +43,8 @@ func (h Controller) GroupUseSkill(data *GroupUseSkillInboundInfo, c *player.Play targetRelation = fight.SkillTargetAlly } h.dispatchFightActionEnvelope(c, fight.NewSkillActionEnvelope(data.SkillId, int(data.ActorIndex), int(data.TargetPos), targetRelation, 0)) - return nil, 0 + c.SendPackCmd(7558, nil) + return nil, -1 } func (h Controller) GroupUseItem(data *GroupUseItemInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { diff --git a/logic/controller/fight_boss野怪和地图怪.go b/logic/controller/fight_boss野怪和地图怪.go index 424d91f00..5f14f8969 100644 --- a/logic/controller/fight_boss野怪和地图怪.go +++ b/logic/controller/fight_boss野怪和地图怪.go @@ -75,54 +75,13 @@ func startMapBossFight( ourPets := p.GetPetInfo(100) oppPets := ai.GetPetInfo(0) if mapNode != nil && mapNode.IsGroupBoss != 0 { - ourSlots := buildGroupBossPetSlots(ourPets, groupBossSlotLimit) - oppSlots := buildGroupBossPetSlots(oppPets, groupBossSlotLimit) - if len(ourSlots) > 0 && len(oppSlots) > 0 { - return fight.NewLegacyGroupFightSingleControllerN(p, ai, ourSlots, oppSlots, fn) + if len(ourPets) > 0 && len(oppPets) > 0 { + return fight.NewLegacyGroupFightSingleController(p, ai, ourPets, oppPets, groupBossSlotLimit, fn) } } return fight.NewFight(p, ai, ourPets, oppPets, fn) } -func buildGroupBossPetSlots(pets []model.PetInfo, slotLimit int) [][]model.PetInfo { - if len(pets) == 0 { - return nil - } - - slots := make([][]model.PetInfo, 0, slotLimit) - for _, pet := range pets { - if pet.Hp == 0 { - continue - } - if slotLimit <= 0 { - slotLimit = 3 - } - if len(slots) < slotLimit { - slots = append(slots, []model.PetInfo{pet}) - continue - } - break - } - if len(slots) == 0 { - return nil - } - var idx int = 0 - for _, pet := range pets[len(slots):] { - if pet.Hp == 0 { - continue - } - for step := 0; step < len(slots); step++ { - slotIdx := (idx + step) % len(slots) - if len(slots[slotIdx]) < 6 { - slots[slotIdx] = append(slots[slotIdx], pet) - idx = (slotIdx + 1) % len(slots) - break - } - } - } - return slots -} - // OnPlayerFightNpcMonster 战斗野怪 func (Controller) OnPlayerFightNpcMonster(req *FightNpcMonsterInboundInfo, p *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { if err = p.CanFight(); err != 0 { diff --git a/logic/controller/nono.go b/logic/controller/nono.go index e420bc746..f28daf54c 100644 --- a/logic/controller/nono.go +++ b/logic/controller/nono.go @@ -64,6 +64,7 @@ func (h *Controller) SwitchFlying(data *SwitchFlyingInboundInfo, c *player.Playe // PlayerPetCure 处理控制器请求。 func (h *Controller) PlayerPetCure(data *PetCureInboundInfo, c *player.Player) (result *nono.PetCureOutboundEmpty, err errorcode.ErrorCode) { //这个时候player应该是空的 _ = data + result = &nono.PetCureOutboundEmpty{} if c.IsArenaHealLocked() { return result, errorcode.ErrorCodes.ErrChampionCannotHeal } @@ -73,6 +74,9 @@ func (h *Controller) PlayerPetCure(data *PetCureInboundInfo, c *player.Player) ( for i := range c.Info.PetList { c.Info.PetList[i].Cure() } + for i := range c.Info.BackupPetList { + c.Info.BackupPetList[i].Cure() + } c.Info.Coins -= nonoPetCureCost return } diff --git a/logic/service/fight/action.go b/logic/service/fight/action.go index 471545b1b..8e177971b 100644 --- a/logic/service/fight/action.go +++ b/logic/service/fight/action.go @@ -82,6 +82,10 @@ func (f *FightC) submitAction(act action.BattleActionI) { break } if replaceIndex >= 0 { + if f.LegacyGroupProtocol { + f.actionMu.Unlock() + return + } f.pendingActions[replaceIndex] = act } else { f.pendingActions = append(f.pendingActions, act) @@ -295,9 +299,8 @@ func (f *FightC) ReadyFight(c common.PlayerI) { } return } - f.Broadcast(func(ff *input.Input) { - - ff.Player.SendPackCmd(2404, &info.S2C_2404{UserID: c.GetInfo().UserID}) + f.BroadcastPlayers(func(p common.PlayerI) { + p.SendPackCmd(2404, &info.S2C_2404{UserID: c.GetInfo().UserID}) }) // 2. 标记当前玩家已准备完成 input := f.GetInputByPlayer(c, false) @@ -369,8 +372,12 @@ func (f *FightC) startBattle(startInfo info.FightStartOutboundInfo) { go f.battleLoop() // 向双方广播战斗开始信息 - f.Broadcast(func(ff *input.Input) { - f.sendFightPacket(ff.Player, fightPacketStart, &startInfo) + f.BroadcastPlayers(func(p common.PlayerI) { + if f.LegacyGroupProtocol { + f.sendLegacyGroupStart(p) + return + } + f.sendFightPacket(p, fightPacketStart, &startInfo) }) }) } diff --git a/logic/service/fight/fightc.go b/logic/service/fight/fightc.go index c7a5c5acb..e8c5cbc43 100644 --- a/logic/service/fight/fightc.go +++ b/logic/service/fight/fightc.go @@ -3,6 +3,7 @@ package fight import ( "blazing/common/utils" + "blazing/logic/service/common" "blazing/logic/service/fight/action" "blazing/logic/service/fight/info" "blazing/logic/service/fight/input" @@ -422,11 +423,19 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) attackValueResult := f.buildNoteUseSkillOutboundInfo() //因为切完才能广播,所以必须和回合结束分开结算 - f.Broadcast(func(fighter *input.Input) { + f.BroadcastPlayers(func(p common.PlayerI) { for _, switchAction := range f.Switch { - if fighter.Player.GetInfo().UserID != switchAction.Reason.UserId { + if p.GetInfo().UserID != switchAction.Reason.UserId { // println("切精灵", switchAction.Reason.UserId, switchAction.Reason.ID) - f.sendFightPacket(fighter.Player, fightPacketChangePetSuccess, &switchAction.Reason) + if f.LegacyGroupProtocol { + switchedInput := f.getInputByUserID(switchAction.Reason.UserId, int(switchAction.Reason.ActorIndex), false) + if switchedInput == nil { + switchedInput = f.getInputByUserID(switchAction.Reason.UserId, int(switchAction.ActorIndex), false) + } + f.sendLegacyGroupChangePetSuccess(p, switchedInput, &switchAction.Reason) + } else { + f.sendFightPacket(p, fightPacketChangePetSuccess, &switchAction.Reason) + } } } }) @@ -440,8 +449,12 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) // }) return } + f.BroadcastPlayers(func(p common.PlayerI) { + if !f.LegacyGroupProtocol { + f.sendFightPacket(p, fightPacketSkillResult, &attackValueResult) + } + }) f.Broadcast(func(fighter *input.Input) { - f.sendFightPacket(fighter.Player, fightPacketSkillResult, &attackValueResult) fighter.CanChange = 0 }) if f.closefight { diff --git a/logic/service/fight/group_legacy.go b/logic/service/fight/group_legacy.go index 4888ce027..334bbca80 100644 --- a/logic/service/fight/group_legacy.go +++ b/logic/service/fight/group_legacy.go @@ -3,6 +3,7 @@ package fight import ( "blazing/logic/service/common" "blazing/logic/service/fight/action" + "blazing/logic/service/fight/info" "blazing/logic/service/fight/input" "blazing/modules/player/model" ) @@ -38,6 +39,17 @@ const ( type fightPacketKind uint8 +const ( + fightPacketReady fightPacketKind = iota + fightPacketStart + fightPacketSkillResult + fightPacketOver + fightPacketChangePetSuccess + fightPacketUseItem + fightPacketChat + fightPacketLoadPercentNotice +) + type legacyEscapeSuccessInfo struct { UserID uint32 `struc:"uint32"` Nick string `struc:"[16]byte"` @@ -50,6 +62,14 @@ type legacyBoutDoneInfo struct { } type legacySpriteDieInfo struct { + Count uint8 `struc:"uint8"` + Side uint8 `struc:"uint8"` + ActorIndex uint8 `struc:"uint8"` + Flag uint8 `struc:"uint8"` + HasBackup uint32 `struc:"uint32"` +} + +type legacyLegacySpriteDieItem struct { Flag uint8 `struc:"uint8"` Side uint8 `struc:"uint8"` ActorIndex uint8 `struc:"uint8"` @@ -57,16 +77,132 @@ type legacySpriteDieInfo struct { HasBackup uint32 `struc:"uint32"` } -const ( - fightPacketReady fightPacketKind = iota - fightPacketStart - fightPacketSkillResult - fightPacketOver - fightPacketChangePetSuccess - fightPacketUseItem - fightPacketChat - fightPacketLoadPercentNotice -) +type legacyGroupReadyToFightInfo struct { + Model uint32 `struc:"uint32"` + GroupOneInfo legacyReadyToFightTeam `struc:""` + GroupTwoInfo legacyReadyToFightTeam `struc:""` +} + +type legacyReadyToFightTeam struct { + InvitorID uint8 `struc:"uint8"` + LeaderID uint32 `struc:"uint32"` + GroupMembCnt uint8 `struc:"sizeof=GroupList"` + GroupList []legacyReadyFightUser `struc:""` +} + +type legacyReadyFightUser struct { + UserID uint32 `struc:"uint32"` + Nick string `struc:"[16]byte"` + MonCnt uint32 `struc:"sizeof=MonList"` + MonList []legacyReadyFightPet `struc:""` +} + +type legacyReadyFightPet struct { + ID uint32 `struc:"uint32"` + MoveCnt uint32 `struc:"sizeof=MoveList"` + MoveList []uint32 `struc:"[]uint32"` +} + +type legacyGroupStartInfo struct { + IsGank uint8 `struc:"uint8"` + GroupOneN uint8 `struc:"sizeof=GroupOne"` + GroupOne []legacyGroupStartPet `struc:""` + GroupTwoN uint8 `struc:"sizeof=GroupTwo"` + GroupTwo []legacyGroupStartPet `struc:""` +} + +type legacyGroupStartPet struct { + Side uint8 `struc:"uint8"` + Pos uint8 `struc:"uint8"` + UserID uint32 `struc:"uint32"` + IsChange uint8 `struc:"uint8"` + PetID uint32 `struc:"uint32"` + CatchTime uint32 `struc:"uint32"` + Hp uint32 `struc:"uint32"` + MaxHp uint32 `struc:"uint32"` + Level uint32 `struc:"uint32"` + Reserve uint32 `struc:"uint32"` + Flag uint32 `struc:"uint32"` +} + +type legacyGroupSkillHurtPacket struct { + IsGank uint8 `struc:"uint8"` + Attack legacyGroupSkillAttackInfo `struc:""` + Attacked legacyGroupSkillDefendInfo `struc:""` +} + +type legacyGroupSkillAttackInfo struct { + IsAttackor uint8 `struc:"uint8"` + Side uint8 `struc:"uint8"` + Pos uint8 `struc:"uint8"` + UserID uint32 `struc:"uint32"` + StatusList [20]uint8 `struc:"[20]byte"` + Reserve1 uint8 `struc:"uint8"` + Reserve2 uint8 `struc:"uint8"` + BatLvList [6]uint8 `struc:"[6]byte"` + PetID uint32 `struc:"uint32"` + MoveID uint32 `struc:"uint32"` + Hp uint32 `struc:"uint32"` + MaxHp uint32 `struc:"uint32"` + MoveCnt uint32 `struc:"sizeof=MoveMap"` + MoveMap []legacyGroupSkillMoveInfo `struc:""` + Flag uint32 `struc:"uint32"` + IsCrit uint32 `struc:"uint32"` + EffectName uint32 `struc:"uint32"` + AtkTimes uint32 `struc:"uint32"` + Dmg int32 `struc:"int32"` + ChgHp int32 `struc:"int32"` + SideEffectLen uint32 `struc:"uint32"` +} + +type legacyGroupSkillDefendInfo struct { + IsAttackor uint8 `struc:"uint8"` + Side uint8 `struc:"uint8"` + Pos uint8 `struc:"uint8"` + UserID uint32 `struc:"uint32"` + StatusList [20]uint8 `struc:"[20]byte"` + Reserve1 uint8 `struc:"uint8"` + Reserve2 uint8 `struc:"uint8"` + BatLvList [6]uint8 `struc:"[6]byte"` + PetID uint32 `struc:"uint32"` + MoveID uint32 `struc:"uint32"` + Hp uint32 `struc:"uint32"` + MaxHp uint32 `struc:"uint32"` + MoveCnt uint32 `struc:"sizeof=MoveMap"` + MoveMap []legacyGroupSkillMoveInfo `struc:""` + Flag uint32 `struc:"uint32"` + SideEffectLen uint32 `struc:"uint32"` +} + +type legacyGroupSkillMoveInfo struct { + MoveID uint32 `struc:"uint32"` + PP uint32 `struc:"uint32"` +} + +type legacyGroupFightOverInfo struct { + IsGank uint8 `struc:"uint8"` + Reason uint32 `struc:"uint32"` + WinnerID uint32 `struc:"uint32"` + Reserve uint32 `struc:"uint32"` + TwoTimes uint32 `struc:"uint32"` + ThreeTimes uint32 `struc:"uint32"` + AutoFightTime uint32 `struc:"uint32"` + Reserve2 uint32 `struc:"uint32"` + EnergyTime uint32 `struc:"uint32"` + LearnTimes uint32 `struc:"uint32"` +} + +type legacyGroupChangePetSuccessInfo struct { + Side uint8 `struc:"uint8"` + Pos uint8 `struc:"uint8"` + UserID uint32 `struc:"uint32"` + PetID uint32 `struc:"uint32"` + CatchTime uint32 `struc:"uint32"` + Level uint32 `struc:"uint32"` + Hp uint32 `struc:"uint32"` + MaxHp uint32 `struc:"uint32"` + SkinID uint32 `struc:"uint32"` +} func groupModelByFight(f *FightC) uint32 { if f == nil { @@ -84,37 +220,15 @@ func groupModelByFight(f *FightC) uint32 { } } -func (f *FightC) sendLegacyGroupOver(player common.PlayerI, over *model.FightOverInfo) { - if player == nil { - return - } - if over == nil { - over = &model.FightOverInfo{} - } - f.sendFightPacket(player, fightPacketOver, over) -} - func (f *FightC) fightPacketCmd(kind fightPacketKind) uint32 { switch kind { case fightPacketReady: - if f != nil && f.LegacyGroupProtocol { - return groupCmdReadyToFight - } return 2503 case fightPacketStart: - if f != nil && f.LegacyGroupProtocol { - return groupCmdStartFight - } return 2504 case fightPacketSkillResult: - if f != nil && f.LegacyGroupProtocol { - return groupCmdSkillHurt - } return 2505 case fightPacketOver: - if f != nil && f.LegacyGroupProtocol { - return groupCmdFightOver - } return 2506 case fightPacketChangePetSuccess: if f != nil && f.LegacyGroupProtocol { @@ -152,6 +266,232 @@ func (f *FightC) sendFightPacket(player common.PlayerI, kind fightPacketKind, pa player.SendPackCmd(cmd, payload) } +func (f *FightC) sendLegacyGroupReady(player common.PlayerI) { + if f == nil || !f.LegacyGroupProtocol || player == nil { + return + } + player.SendPackCmd(groupCmdReadyToFight, f.buildLegacyGroupReadyInfo()) +} + +func (f *FightC) buildLegacyGroupReadyInfo() *legacyGroupReadyToFightInfo { + return &legacyGroupReadyToFightInfo{ + Model: groupModelByFight(f), + GroupOneInfo: f.buildLegacyReadyTeam(f.OurPlayers, f.Our), + GroupTwoInfo: f.buildLegacyReadyTeam(f.OppPlayers, f.Opp), + } +} + +func (f *FightC) buildLegacyReadyTeam(players []common.PlayerI, inputs []*input.Input) legacyReadyToFightTeam { + team := legacyReadyToFightTeam{InvitorID: 1} + users := make([]legacyReadyFightUser, 0, len(players)) + for _, p := range players { + if p == nil || p.GetInfo() == nil { + continue + } + users = append(users, legacyReadyFightUser{ + UserID: p.GetInfo().UserID, + Nick: p.GetInfo().Nick, + MonList: collectLegacyReadyPetsByController(inputs, p.GetInfo().UserID), + }) + } + if len(users) == 0 { + if fallback := firstNonNilInput(inputs); fallback != nil && fallback.Player != nil && fallback.Player.GetInfo() != nil { + info := fallback.Player.GetInfo() + users = append(users, legacyReadyFightUser{ + UserID: info.UserID, + Nick: info.Nick, + MonList: collectLegacyReadyPetsByController(inputs, info.UserID), + }) + } + } + for idx := range users { + users[idx].MonCnt = uint32(len(users[idx].MonList)) + } + if len(users) > 0 { + team.LeaderID = users[0].UserID + } + team.GroupList = users + return team +} + +func collectLegacyReadyPetsByController(inputs []*input.Input, controllerID uint32) []legacyReadyFightPet { + pets := make([]legacyReadyFightPet, 0, 6) + for _, in := range inputs { + if in == nil || !in.ControlledBy(controllerID) { + continue + } + currentPet := in.CurrentPet() + if currentPet == nil { + continue + } + pets = append(pets, buildLegacyReadyFightPet(currentPet)) + } + return pets +} + +func buildLegacyReadyFightPet(pet *info.BattlePetEntity) legacyReadyFightPet { + result := legacyReadyFightPet{} + if pet == nil { + return result + } + moves := make([]uint32, 0, len(pet.Info.SkillList)) + for _, skill := range pet.Info.SkillList { + if skill.ID == 0 { + continue + } + moves = append(moves, skill.ID) + } + result.ID = pet.Info.ID + result.MoveList = moves + return result +} + +func firstNonNilInput(inputs []*input.Input) *input.Input { + for _, in := range inputs { + if in != nil { + return in + } + } + return nil +} + +func (f *FightC) sendLegacyGroupStart(player common.PlayerI) { + if f == nil || !f.LegacyGroupProtocol || player == nil { + return + } + player.SendPackCmd(groupCmdStartFight, f.buildLegacyGroupStartInfo()) +} + +func (f *FightC) buildLegacyGroupStartInfo() *legacyGroupStartInfo { + return &legacyGroupStartInfo{ + IsGank: 0, + GroupOne: f.collectLegacyGroupStartPets(f.Our, 1), + GroupTwo: f.collectLegacyGroupStartPets(f.Opp, 2), + } +} + +func (f *FightC) collectLegacyGroupStartPets(inputs []*input.Input, side uint8) []legacyGroupStartPet { + ret := make([]legacyGroupStartPet, 0, len(inputs)) + for pos, in := range inputs { + if in == nil { + continue + } + currentPet := in.CurrentPet() + if currentPet == nil { + continue + } + userID := uint32(0) + if in.Player != nil && in.Player.GetInfo() != nil { + userID = in.Player.GetInfo().UserID + } + ret = append(ret, legacyGroupStartPet{ + Side: side, + Pos: uint8(pos), + UserID: userID, + IsChange: 0, + PetID: currentPet.Info.ID, + CatchTime: currentPet.Info.CatchTime, + Hp: currentPet.Info.Hp, + MaxHp: currentPet.Info.MaxHp, + Level: currentPet.Info.Level, + Reserve: 0, + Flag: 1, + }) + } + return ret +} + +func (f *FightC) sendLegacyGroupOver(player common.PlayerI, over *model.FightOverInfo) { + if f == nil || !f.LegacyGroupProtocol || player == nil { + return + } + player.SendPackCmd(groupCmdFightOver, f.buildLegacyGroupOverInfo(over)) +} + +func (f *FightC) buildLegacyGroupOverInfo(over *model.FightOverInfo) *legacyGroupFightOverInfo { + result := &legacyGroupFightOverInfo{} + if over != nil { + result.Reason = resolveLegacyGroupFightOverReason(over) + result.WinnerID = over.WinnerId + } + if our := f.primaryOurPlayer(); our != nil && our.GetInfo() != nil { + playerInfo := our.GetInfo() + result.TwoTimes = uint32(playerInfo.TwoTimes) + result.ThreeTimes = uint32(playerInfo.ThreeTimes) + result.AutoFightTime = playerInfo.AutoFightTime + result.EnergyTime = uint32(playerInfo.EnergyTime) + result.LearnTimes = playerInfo.LearnTimes + } + return result +} + +func mapLegacyGroupFightOverReason(reason model.EnumBattleOverReason) uint32 { + switch reason { + case model.BattleOverReason.PlayerOffline: + return 2 + case model.BattleOverReason.PlayerOVerTime: + return 3 + case model.BattleOverReason.NOTwind: + return 4 + case model.BattleOverReason.DefaultEnd: + return 1 + case model.BattleOverReason.PlayerEscape: + return 6 + default: + return 5 + } +} + +func resolveLegacyGroupFightOverReason(over *model.FightOverInfo) uint32 { + if over == nil { + return 5 + } + switch over.Reason { + case model.BattleOverReason.PlayerOffline: + return 2 + case model.BattleOverReason.PlayerOVerTime: + return 3 + case model.BattleOverReason.PlayerEscape: + return 6 + case model.BattleOverReason.NOTwind: + return 4 + } + if over.WinnerId != 0 { + return 1 + } + return mapLegacyGroupFightOverReason(over.Reason) +} + +func (f *FightC) sendLegacyGroupChangePetSuccess(player common.PlayerI, in *input.Input, reason *info.ChangePetInfo) { + if f == nil || !f.LegacyGroupProtocol || player == nil || in == nil || reason == nil { + return + } + player.SendPackCmd(groupCmdChangePetSuc, f.buildLegacyGroupChangePetSuccessInfo(in, reason)) +} + +func (f *FightC) buildLegacyGroupChangePetSuccessInfo(in *input.Input, reason *info.ChangePetInfo) *legacyGroupChangePetSuccessInfo { + result := &legacyGroupChangePetSuccessInfo{} + if in == nil || reason == nil { + return result + } + if !f.isOurPlayerID(in.UserID) { + result.Side = 2 + } else { + result.Side = 1 + } + result.Pos = uint8(in.TeamSlotIndex()) + result.UserID = reason.UserId + result.PetID = reason.ID + result.CatchTime = reason.CatchTime + result.Level = reason.Level + result.Hp = reason.Hp + result.MaxHp = reason.MaxHp + if currentPet := in.CurrentPet(); currentPet != nil { + result.SkinID = currentPet.Info.SkinID + } + return result +} + func (f *FightC) SendLegacyEscapeSuccess(player common.PlayerI, actorIndex int) { if f == nil || !f.LegacyGroupProtocol || player == nil { return @@ -166,11 +506,8 @@ func (f *FightC) SendLegacyEscapeSuccess(player common.PlayerI, actorIndex int) Side: side, ActorIndex: uint8(actorIndex), } - f.Broadcast(func(ff *input.Input) { - if ff == nil || ff.Player == nil { - return - } - ff.Player.SendPackCmd(groupCmdEscapeSuc, &payload) + f.BroadcastPlayers(func(p common.PlayerI) { + p.SendPackCmd(groupCmdEscapeSuc, &payload) }) } @@ -178,19 +515,147 @@ func (f *FightC) sendLegacyRoundBroadcast(firstAttack, secondAttack *action.Sele if f == nil || !f.LegacyGroupProtocol { return } + if firstAttack != nil { + f.sendLegacyGroupSkillHurt(firstAttack) + } + if secondAttack != nil { + f.sendLegacyGroupSkillHurt(secondAttack) + } f.sendLegacyGroupBoutDone() } +func (f *FightC) sendLegacyGroupSkillHurt(skillAction *action.SelectSkillAction) { + if f == nil || !f.LegacyGroupProtocol || skillAction == nil { + return + } + packet := f.buildLegacyGroupSkillHurtPacket(skillAction) + if packet == nil { + return + } + f.BroadcastPlayers(func(p common.PlayerI) { + p.SendPackCmd(groupCmdSkillHurt, packet) + }) +} + +func (f *FightC) buildLegacyGroupSkillHurtPacket(skillAction *action.SelectSkillAction) *legacyGroupSkillHurtPacket { + attacker := f.GetInputByAction(skillAction, false) + defender := f.GetInputByAction(skillAction, true) + if attacker == nil || defender == nil { + return nil + } + return &legacyGroupSkillHurtPacket{ + IsGank: 0, + Attack: f.buildLegacyGroupSkillAttackInfo(skillAction, attacker), + Attacked: f.buildLegacyGroupSkillDefendInfo(defender), + } +} + +func (f *FightC) fillLegacyGroupSkillCommonFields( + self *input.Input, + isAttackor uint8, + statusList *[20]uint8, + batLvList *[6]uint8, +) (side uint8, pos uint8, userID uint32, petID uint32, hp uint32, maxHP uint32, moveMap []legacyGroupSkillMoveInfo, flag uint32) { + if self == nil { + return + } + if !f.isOurPlayerID(self.UserID) { + side = 2 + } else { + side = 1 + } + pos = uint8(self.TeamSlotIndex()) + userID = self.UserID + attackValue := self.AttackValue + if attackValue == nil { + attackValue = info.NewAttackValue(self.UserID) + } + for i := 0; i < len(attackValue.Status) && i < 20; i++ { + statusList[i] = uint8(attackValue.Status[i]) + } + for i := 0; i < len(attackValue.Prop) && i < len(batLvList); i++ { + batLvList[i] = uint8(attackValue.Prop[i]) + } + currentPet := self.CurrentPet() + if currentPet != nil { + petID = currentPet.Info.ID + hp = currentPet.Info.Hp + maxHP = currentPet.Info.MaxHp + moveMap = collectLegacyGroupSkillMoves(currentPet.Info.SkillList) + } else { + hp = clampLegacyInt32ToUint32(attackValue.RemainHp) + maxHP = attackValue.MaxHp + moveMap = collectLegacyGroupSkillMoves(attackValue.SkillList) + } + flag = attackValue.State + return +} + +func (f *FightC) buildLegacyGroupSkillAttackInfo(skillAction *action.SelectSkillAction, self *input.Input) legacyGroupSkillAttackInfo { + result := legacyGroupSkillAttackInfo{} + if self == nil { + return result + } + result.IsAttackor = 0 + result.Side, result.Pos, result.UserID, result.PetID, result.Hp, result.MaxHp, result.MoveMap, result.Flag = + f.fillLegacyGroupSkillCommonFields(self, result.IsAttackor, &result.StatusList, &result.BatLvList) + attackValue := self.AttackValue + if attackValue == nil { + attackValue = info.NewAttackValue(self.UserID) + } + if skillAction != nil && skillAction.SkillEntity != nil { + result.MoveID = uint32(skillAction.SkillEntity.XML.ID) + } else { + result.MoveID = attackValue.SkillID + } + result.IsCrit = attackValue.IsCritical + result.EffectName = attackValue.State + result.AtkTimes = 1 + result.Dmg = int32(attackValue.LostHp) + result.ChgHp = attackValue.GainHp + return result +} + +func (f *FightC) buildLegacyGroupSkillDefendInfo(self *input.Input) legacyGroupSkillDefendInfo { + result := legacyGroupSkillDefendInfo{} + if self == nil { + return result + } + result.IsAttackor = 1 + result.Side, result.Pos, result.UserID, result.PetID, result.Hp, result.MaxHp, result.MoveMap, result.Flag = + f.fillLegacyGroupSkillCommonFields(self, result.IsAttackor, &result.StatusList, &result.BatLvList) + result.MoveID = 0 + return result +} + +func collectLegacyGroupSkillMoves(skills []model.SkillInfo) []legacyGroupSkillMoveInfo { + moves := make([]legacyGroupSkillMoveInfo, 0, len(skills)) + for _, skill := range skills { + if skill.ID == 0 { + continue + } + moves = append(moves, legacyGroupSkillMoveInfo{ + MoveID: skill.ID, + PP: skill.PP, + }) + } + return moves +} + +func clampLegacyInt32ToUint32(v int32) uint32 { + if v < 0 { + return 0 + } + return uint32(v) +} + func (f *FightC) sendLegacyGroupBoutDone() { if f == nil || !f.LegacyGroupProtocol { return } payload := legacyBoutDoneInfo{Round: f.Round} - f.Broadcast(func(ff *input.Input) { - if ff == nil || ff.Player == nil { - return - } - ff.Player.SendPackCmd(groupCmdBoutDone, &payload) + f.BroadcastPlayers(func(p common.PlayerI) { + p.SendPackCmd(groupCmdBoutDone, &payload) }) } @@ -207,16 +672,13 @@ func (f *FightC) sendLegacySpriteDie(in *input.Input, hasBackup bool) { data = 1 } payload := legacySpriteDieInfo{ - Flag: 1, + Count: 1, Side: side, ActorIndex: uint8(in.TeamSlotIndex()), - Reserve: 1, + Flag: 1, HasBackup: data, } - f.Broadcast(func(ff *input.Input) { - if ff == nil || ff.Player == nil { - return - } - ff.Player.SendPackCmd(groupCmdSpriteDie, &payload) + f.BroadcastPlayers(func(p common.PlayerI) { + p.SendPackCmd(groupCmdSpriteDie, &payload) }) } diff --git a/logic/service/fight/input.go b/logic/service/fight/input.go index 6c8b00766..4728b4224 100644 --- a/logic/service/fight/input.go +++ b/logic/service/fight/input.go @@ -528,6 +528,27 @@ func (f *FightC) Broadcast(t func(ff *input.Input)) { } +func (f *FightC) BroadcastPlayers(t func(common.PlayerI)) { + if f == nil || t == nil { + return + } + seen := make(map[uint32]struct{}, len(f.OurPlayers)+len(f.OppPlayers)) + visit := func(players []common.PlayerI) { + for _, p := range players { + if p == nil || p.GetInfo() == nil { + continue + } + if _, ok := seen[p.GetInfo().UserID]; ok { + continue + } + seen[p.GetInfo().UserID] = struct{}{} + t(p) + } + } + visit(f.OurPlayers) + visit(f.OppPlayers) +} + func (f *FightC) GetOverChan() chan struct{} { return f.over diff --git a/logic/service/fight/loop.go b/logic/service/fight/loop.go index 3b93d847a..6ab0dde81 100644 --- a/logic/service/fight/loop.go +++ b/logic/service/fight/loop.go @@ -173,10 +173,14 @@ func (f *FightC) battleLoop() { //大乱斗,给个延迟 //<-time.After(1000) - f.Broadcast(func(ff *input.Input) { - f.sendFightPacket(ff.Player, fightPacketOver, &f.FightOverInfo) + f.BroadcastPlayers(func(p common.PlayerI) { + if f.LegacyGroupProtocol { + f.sendLegacyGroupOver(p, &f.FightOverInfo) + } else { + f.sendFightPacket(p, fightPacketOver, &f.FightOverInfo) + } - ff.Player.QuitFight() + p.QuitFight() //待退出玩家战斗状态 }) @@ -260,7 +264,11 @@ func (f *FightC) collectPlayerActions(expectedSlots map[actionSlotKey]struct{}) ret.Reason = reason ret.Reason.ActorIndex = uint32(ret.ActorIndex) - f.sendFightPacket(selfinput.Player, fightPacketChangePetSuccess, &ret.Reason) + if f.LegacyGroupProtocol { + f.sendLegacyGroupChangePetSuccess(selfinput.Player, selfinput, &ret.Reason) + } else { + f.sendFightPacket(selfinput.Player, fightPacketChangePetSuccess, &ret.Reason) + } f.Switch[key] = ret @@ -281,7 +289,11 @@ func (f *FightC) collectPlayerActions(expectedSlots map[actionSlotKey]struct{}) selfinput.CanChange = 0 if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC && paction.GetPlayerID() == 0 { f.Switch = make(map[actionSlotKey]*action.ActiveSwitchAction) - f.sendFightPacket(f.Our[0].Player, fightPacketChangePetSuccess, &ret.Reason) + if f.LegacyGroupProtocol { + f.sendLegacyGroupChangePetSuccess(f.Our[0].Player, selfinput, &ret.Reason) + } else { + f.sendFightPacket(f.Our[0].Player, fightPacketChangePetSuccess, &ret.Reason) + } //println("AI出手死切") f.triggerNPCActions() // boss出手后获取出招 @@ -597,12 +609,12 @@ func (f *FightC) handleItemAction(a *action.UseItemAction) { case gconv.Int(item.HP) != 0: addhp := item.HP source.Heal(source, a, alpacadecimal.NewFromInt(int64(addhp))) - f.Broadcast(func(ff *input.Input) { + f.BroadcastPlayers(func(p common.PlayerI) { currentPet := source.PrimaryCurPet() if currentPet == nil { return } - f.sendFightPacket(ff.Player, fightPacketUseItem, &info.UsePetIteminfo{ + f.sendFightPacket(p, fightPacketUseItem, &info.UsePetIteminfo{ UserID: source.UserID, ChangeHp: int32(addhp), ItemID: uint32(item.ID), @@ -612,12 +624,12 @@ func (f *FightC) handleItemAction(a *action.UseItemAction) { }) case gconv.Int(item.PP) != 0: source.HealPP(item.PP) - f.Broadcast(func(ff *input.Input) { + f.BroadcastPlayers(func(p common.PlayerI) { currentPet := source.PrimaryCurPet() if currentPet == nil { return } - f.sendFightPacket(ff.Player, fightPacketUseItem, &info.UsePetIteminfo{ + f.sendFightPacket(p, fightPacketUseItem, &info.UsePetIteminfo{ UserID: source.UserID, ItemID: uint32(item.ID), diff --git a/logic/service/fight/new.go b/logic/service/fight/new.go index bdef0cd4c..8693ee81d 100644 --- a/logic/service/fight/new.go +++ b/logic/service/fight/new.go @@ -47,6 +47,100 @@ func NewFightSingleControllerN( ) } +// ArrangePetsBySlotLimit 按站位上限切分宠物。 +// 规则: +// 1. 前 slotLimit 只存活宠物优先占据出战位。 +// 2. 其余宠物按 1..slotLimit 轮转挂到对应站位作为后备。 +// 3. 每个站位最多保留 6 只宠物。 +func ArrangePetsBySlotLimit(pets []model.PetInfo, slotLimit int) [][]model.PetInfo { + var ( + alivePets []model.PetInfo + slots [][]model.PetInfo + idx int + ) + for _, pet := range pets { + if pet.Hp == 0 { + continue + } + alivePets = append(alivePets, pet) + } + if len(alivePets) == 0 { + return nil + } + if slotLimit <= 0 { + slotLimit = 1 + } + if slotLimit > len(alivePets) { + slotLimit = len(alivePets) + } + slots = make([][]model.PetInfo, 0, slotLimit) + for i := 0; i < slotLimit; i++ { + slots = append(slots, []model.PetInfo{alivePets[i]}) + } + for _, pet := range alivePets[slotLimit:] { + for step := 0; step < len(slots); step++ { + slotIdx := (idx + step) % len(slots) + if len(slots[slotIdx]) >= 6 { + continue + } + slots[slotIdx] = append(slots[slotIdx], pet) + idx = (slotIdx + 1) % len(slots) + break + } + } + return slots +} + +// ExpandPlayersWithSlotLimit 将“每位玩家的宠物列表”按站位限制展开为扁平站位列表。 +// 例如: +// 1. slotLimit=1: 每位玩家占 1 个站位,其余为该站位后备。 +// 2. slotLimit=3: 每位玩家最多展开 3 个站位,每个站位带各自后备。 +func ExpandPlayersWithSlotLimit( + players []common.PlayerI, + petsByPlayer [][]model.PetInfo, + slotLimit int, +) ([]common.PlayerI, [][]model.PetInfo, errorcode.ErrorCode) { + var ( + flatPlayers []common.PlayerI + flatSlots [][]model.PetInfo + ) + if len(players) == 0 || len(players) != len(petsByPlayer) { + return nil, nil, errorcode.ErrorCodes.ErrSystemBusyTryLater + } + for idx, p := range players { + if p == nil { + return nil, nil, errorcode.ErrorCodes.ErrSystemBusyTryLater + } + slots := ArrangePetsBySlotLimit(petsByPlayer[idx], slotLimit) + for _, slotPets := range slots { + flatPlayers = append(flatPlayers, p) + flatSlots = append(flatSlots, slotPets) + } + } + if len(flatPlayers) == 0 || len(flatPlayers) != len(flatSlots) { + return nil, nil, errorcode.ErrorCodes.ErrSystemBusyTryLater + } + return flatPlayers, flatSlots, 0 +} + +// NewFightSingleController 使用站位限制规则创建单人控制多站位战斗。 +func NewFightSingleController( + ourController common.PlayerI, + oppController common.PlayerI, + ourPets []model.PetInfo, + oppPets []model.PetInfo, + slotLimit int, + fn func(model.FightOverInfo), +) (*FightC, errorcode.ErrorCode) { + return NewFightSingleControllerN( + ourController, + oppController, + ArrangePetsBySlotLimit(ourPets, slotLimit), + ArrangePetsBySlotLimit(oppPets, slotLimit), + fn, + ) +} + // NewLegacyGroupFightSingleControllerN 创建旧组队协议的单人控制多站位战斗。 func NewLegacyGroupFightSingleControllerN( ourController common.PlayerI, @@ -82,6 +176,24 @@ func NewLegacyGroupFightSingleControllerN( ) } +// NewLegacyGroupFightSingleController 使用站位限制规则创建旧组队协议战斗。 +func NewLegacyGroupFightSingleController( + ourController common.PlayerI, + oppController common.PlayerI, + ourPets []model.PetInfo, + oppPets []model.PetInfo, + slotLimit int, + fn func(model.FightOverInfo), +) (*FightC, errorcode.ErrorCode) { + return NewLegacyGroupFightSingleControllerN( + ourController, + oppController, + ArrangePetsBySlotLimit(ourPets, slotLimit), + ArrangePetsBySlotLimit(oppPets, slotLimit), + fn, + ) +} + // NewFightPerSlotControllerN 创建 N 打战斗(多人各控制一个站位)。 // ourPlayers/oppPlayers 与 ourPetsBySlot/oppPetsBySlot 按站位一一对应。 func NewFightPerSlotControllerN( @@ -117,25 +229,32 @@ func NewFightPerSlotControllerN( ) } +// NewFightPerPlayerControllers 使用“每位玩家 + 站位限制”创建多人战斗。 +func NewFightPerPlayerControllers( + ourPlayers []common.PlayerI, + oppPlayers []common.PlayerI, + ourPetsByPlayer [][]model.PetInfo, + oppPetsByPlayer [][]model.PetInfo, + slotLimit int, + fn func(model.FightOverInfo), +) (*FightC, errorcode.ErrorCode) { + flatOurPlayers, flatOurSlots, err := ExpandPlayersWithSlotLimit(ourPlayers, ourPetsByPlayer, slotLimit) + if err > 0 { + return nil, err + } + flatOppPlayers, flatOppSlots, err := ExpandPlayersWithSlotLimit(oppPlayers, oppPetsByPlayer, slotLimit) + if err > 0 { + return nil, err + } + return NewFightPerSlotControllerN(flatOurPlayers, flatOppPlayers, flatOurSlots, flatOppSlots, fn) +} + // 创建新战斗,邀请方和被邀请方,或者玩家和野怪方 func NewFight(p1, p2 common.PlayerI, b1, b2 []model.PetInfo, fn func(model.FightOverInfo)) (*FightC, errorcode.ErrorCode) { if p1 == nil || p2 == nil { return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater } - fightInfo := p1.Getfightinfo() - ourInput, err := buildInputFromPets(p1, b1, fightInfo.Mode) - if err > 0 { - return nil, err - } - oppInput, err := buildInputFromPets(p2, b2, fightInfo.Mode) - if err > 0 { - return nil, err - } - return NewFightWithOptions( - WithFightInputs([]*input.Input{ourInput}, []*input.Input{oppInput}), - WithFightCallback(fn), - WithFightInfo(fightInfo), - ) + return NewFightSingleController(p1, p2, b1, b2, 1, fn) } // buildFight 基于已准备好的双方 Inputs 构建战斗实例。 @@ -196,8 +315,12 @@ func buildFight(opts *fightBuildOptions) (*FightC, errorcode.ErrorCode) { } f.FightStartOutboundInfo = f.buildFightStartInfo() - f.Broadcast(func(ff *input.Input) { - f.sendFightPacket(ff.Player, fightPacketReady, &f.ReadyInfo) + f.BroadcastPlayers(func(p common.PlayerI) { + if f.LegacyGroupProtocol { + f.sendLegacyGroupReady(p) + return + } + f.sendFightPacket(p, fightPacketReady, &f.ReadyInfo) }) cool.Cron.AfterFunc(loadtime, func() { @@ -215,9 +338,13 @@ func buildFight(opts *fightBuildOptions) (*FightC, errorcode.ErrorCode) { case !our.Finished: f.WinnerId = opp.Player.GetInfo().UserID } - f.Broadcast(func(ff *input.Input) { - f.sendFightPacket(ff.Player, fightPacketOver, &f.FightOverInfo) - ff.Player.QuitFight() + f.BroadcastPlayers(func(p common.PlayerI) { + if f.LegacyGroupProtocol { + f.sendLegacyGroupOver(p, &f.FightOverInfo) + } else { + f.sendFightPacket(p, fightPacketOver, &f.FightOverInfo) + } + p.QuitFight() }) } }) diff --git a/logic/service/fight/slot_alloc_test.go b/logic/service/fight/slot_alloc_test.go new file mode 100644 index 000000000..1df0b8d96 --- /dev/null +++ b/logic/service/fight/slot_alloc_test.go @@ -0,0 +1,60 @@ +package fight + +import ( + "blazing/logic/service/common" + "blazing/modules/player/model" + "testing" +) + +func TestArrangePetsBySlotLimit(t *testing.T) { + pets := []model.PetInfo{ + {ID: 1, Hp: 10}, + {ID: 2, Hp: 10}, + {ID: 3, Hp: 10}, + {ID: 4, Hp: 10}, + {ID: 5, Hp: 10}, + {ID: 6, Hp: 10}, + } + + slots := ArrangePetsBySlotLimit(pets, 3) + if len(slots) != 3 { + t.Fatalf("expected 3 slots, got %d", len(slots)) + } + if len(slots[0]) != 2 || slots[0][0].ID != 1 || slots[0][1].ID != 4 { + t.Fatalf("slot 0 mismatch: %+v", slots[0]) + } + if len(slots[1]) != 2 || slots[1][0].ID != 2 || slots[1][1].ID != 5 { + t.Fatalf("slot 1 mismatch: %+v", slots[1]) + } + if len(slots[2]) != 2 || slots[2][0].ID != 3 || slots[2][1].ID != 6 { + t.Fatalf("slot 2 mismatch: %+v", slots[2]) + } +} + +func TestExpandPlayersWithSlotLimit(t *testing.T) { + players := []common.PlayerI{&stubPlayer{}, &stubPlayer{}} + petsByPlayer := [][]model.PetInfo{ + { + {ID: 1, Hp: 10}, + {ID: 2, Hp: 10}, + {ID: 3, Hp: 10}, + }, + { + {ID: 11, Hp: 10}, + {ID: 12, Hp: 10}, + }, + } + flatPlayers, flatSlots, err := ExpandPlayersWithSlotLimit(players, petsByPlayer, 1) + if err != 0 { + t.Fatalf("unexpected err: %v", err) + } + if len(flatPlayers) != 2 || len(flatSlots) != 2 { + t.Fatalf("unexpected flatten result: players=%d slots=%d", len(flatPlayers), len(flatSlots)) + } + if len(flatSlots[0]) != 3 || flatSlots[0][0].ID != 1 || flatSlots[0][1].ID != 2 || flatSlots[0][2].ID != 3 { + t.Fatalf("player0 slot mismatch: %+v", flatSlots[0]) + } + if len(flatSlots[1]) != 2 || flatSlots[1][0].ID != 11 || flatSlots[1][1].ID != 12 { + t.Fatalf("player1 slot mismatch: %+v", flatSlots[1]) + } +}