diff --git a/logic/controller/fight_pvp_withplayer.go b/logic/controller/fight_pvp_withplayer.go index 4baac4300..210f62400 100644 --- a/logic/controller/fight_pvp_withplayer.go +++ b/logic/controller/fight_pvp_withplayer.go @@ -13,7 +13,7 @@ import ( // 接收战斗或者取消战斗的包 func (h Controller) OnPlayerHandleFightInvite(data *fight.HandleFightInviteInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { - if c.GetSpace().Owner.UserID == c.Info.UserID { + if c.IsArenaPVPLocked() { return nil, errorcode.ErrorCodes.ErrSystemError } if c.GetSpace().Owner.UserID == data.UserID { @@ -79,7 +79,7 @@ func (h Controller) OnPlayerHandleFightInvite(data *fight.HandleFightInviteInbou // 邀请其他人进行战斗 func (h Controller) OnPlayerInviteOtherFight(data *fight.InviteToFightInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { - if c.GetSpace().Owner.UserID == c.Info.UserID { + if c.IsArenaPVPLocked() { return nil, errorcode.ErrorCodes.ErrSystemError } if c.GetSpace().Owner.ChallengerID == c.Info.UserID { diff --git a/logic/controller/fight_擂台.go b/logic/controller/fight_擂台.go index 1beab4bdf..2ea6cee2f 100644 --- a/logic/controller/fight_擂台.go +++ b/logic/controller/fight_擂台.go @@ -47,7 +47,7 @@ func (h Controller) ArenaFightOwner(data1 *fight.ARENA_FIGHT_OWENR, c *player.Pl return nil, r } - if c.Info.UserID == c.GetSpace().Owner.UserID { + if c.IsArenaHost() { return nil, errorcode.ErrorCodes.ErrNoEligiblePokemon } diff --git a/logic/controller/nono.go b/logic/controller/nono.go index 9851a8276..a6cc1bf6a 100644 --- a/logic/controller/nono.go +++ b/logic/controller/nono.go @@ -61,7 +61,7 @@ func (h *Controller) SwitchFlying(data *nono.SwitchFlyingInboundInfo, c *player. func (h *Controller) PlayerPetCure(data *nono.PetCureInboundInfo, c *player.Player) (result *nono.PetCureOutboundEmpty, err errorcode.ErrorCode) { //这个时候player应该是空的 _ = data - if c.GetSpace().Owner.UserID == c.Info.UserID { + if c.IsArenaHealLocked() { return result, errorcode.ErrorCodes.ErrChampionCannotHeal } if !c.GetCoins(nonoPetCureCost) { diff --git a/logic/controller/pet_info.go b/logic/controller/pet_info.go index 3cd14bb30..0bdf762c2 100644 --- a/logic/controller/pet_info.go +++ b/logic/controller/pet_info.go @@ -306,7 +306,7 @@ func (h Controller) TogglePetBagWarehouse( result = &pet.PetReleaseOutboundInfo{} result.Flag = uint32(data.Flag) - if player.GetSpace().Owner.UserID == player.Info.UserID { + if player.IsArenaSwitchLocked() { return result, errorcode.ErrorCodes.ErrChampionCannotSwitch } @@ -380,7 +380,7 @@ func (h Controller) TogglePetBagWarehouseLegacy( result = &pet.PetReleaseOutboundInfo{} result.Flag = uint32(data.Flag) //擂台住不能换精灵 - if player.GetSpace().Owner.UserID == player.Info.UserID { + if player.IsArenaSwitchLocked() { return result, errorcode.ErrorCodes.ErrChampionCannotSwitch } switch data.Flag { @@ -464,7 +464,7 @@ func (h Controller) PlayerShowPet( // PetOneCure 单体治疗 func (h Controller) PetOneCure( data *pet.PetOneCureInboundInfo, player *player.Player) (result *pet.PetOneCureOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 - if player.GetSpace().Owner.UserID == player.Info.UserID { + if player.IsArenaHealLocked() { return result, errorcode.ErrorCodes.ErrChampionCannotHeal } @@ -484,7 +484,7 @@ func (h Controller) PetOneCure( func (h Controller) PetFirst( data *pet.PetDefaultInboundInfo, player *player.Player) (result *pet.PetDefaultOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 //擂台住不能换精灵 - if player.GetSpace().Owner.UserID == player.Info.UserID { + if player.IsArenaSwitchLocked() { return result, errorcode.ErrorCodes.ErrChampionCannotSwitch } diff --git a/logic/service/fight/action.go b/logic/service/fight/action.go index 9b6d2d2af..63165558f 100644 --- a/logic/service/fight/action.go +++ b/logic/service/fight/action.go @@ -1,14 +1,11 @@ package fight import ( - "blazing/cool" "blazing/logic/service/common" "blazing/logic/service/fight/action" "blazing/logic/service/fight/info" "blazing/logic/service/fight/input" "blazing/modules/player/model" - "context" - "time" "github.com/jinzhu/copier" ) @@ -26,6 +23,83 @@ func (*FightC) Compare(a, b action.BattleActionI) (action.BattleActionI, action. return a, b // 速度相同时,发起方优先 } +const maxPendingActionsPerPlayer = 2 + +func (f *FightC) openActionWindow() { + f.actionMu.Lock() + f.acceptActions = true + f.pendingActions = f.pendingActions[:0] + f.actionMu.Unlock() +} + +func (f *FightC) closeActionWindow() { + f.actionMu.Lock() + f.acceptActions = false + f.pendingActions = f.pendingActions[:0] + f.actionMu.Unlock() +} + +func (f *FightC) submitAction(act action.BattleActionI) { + if act == nil || f.closefight { + return + } + + f.actionMu.Lock() + if !f.acceptActions { + f.actionMu.Unlock() + return + } + count := 0 + replaceIndex := -1 + for i, pending := range f.pendingActions { + if pending == nil || pending.GetPlayerID() != act.GetPlayerID() { + continue + } + count++ + replaceIndex = i + } + if count >= maxPendingActionsPerPlayer && replaceIndex >= 0 { + f.pendingActions[replaceIndex] = act + } else { + f.pendingActions = append(f.pendingActions, act) + } + notify := f.actionNotify + f.actionMu.Unlock() + + if notify == nil { + return + } + + select { + case notify <- struct{}{}: + default: + } +} + +func (f *FightC) nextAction() action.BattleActionI { + f.actionMu.Lock() + if len(f.pendingActions) == 0 { + f.actionMu.Unlock() + return nil + } + + act := f.pendingActions[0] + copy(f.pendingActions, f.pendingActions[1:]) + f.pendingActions = f.pendingActions[:len(f.pendingActions)-1] + hasMore := len(f.pendingActions) > 0 + notify := f.actionNotify + f.actionMu.Unlock() + + if hasMore && notify != nil { + select { + case notify <- struct{}{}: + default: + } + } + + return act +} + // 玩家逃跑/无响应/掉线 func (f *FightC) Over(c common.PlayerI, res model.EnumBattleOverReason) { if f.closefight { @@ -75,15 +149,7 @@ func (f *FightC) ChangePet(c common.PlayerI, id uint32) { BaseAction: action.NewBaseAction(c.GetInfo().UserID), Cid: id, } - select { - case f.actionChan <- ret: - // 发送成功,可选记录日志 - // log.Printf("send skill success, userID: %d, skillID: %d", c.GetInfo().UserID, id) - case <-time.After(time.Second * 60): - // 通道满时的降级处理 - cool.Logger.Printf(context.Background(), "actionChan is full, failed to send skill, userID: %d, skillID: %d", - c.GetInfo().UserID, id) - } + f.submitAction(ret) } @@ -116,16 +182,7 @@ func (f *FightC) UseSkill(c common.PlayerI, id uint32) { } } - // 非阻塞发送,避免goroutine永久阻塞 - select { - case f.actionChan <- ret: - // 发送成功,可选记录日志 - // log.Printf("send skill success, userID: %d, skillID: %d", c.GetInfo().UserID, id) - case <-time.After(time.Second * 60): - // 通道满时的降级处理 - cool.Logger.Printf(context.Background(), "actionChan is full, failed to send skill, userID: %d, skillID: %d", - c.GetInfo().UserID, id) - } + f.submitAction(ret) } // 玩家使用技能 @@ -134,15 +191,7 @@ func (f *FightC) Capture(c common.PlayerI, id uint32) { return } - select { - case f.actionChan <- &action.UseItemAction{BaseAction: action.NewBaseAction(c.GetInfo().UserID), ItemID: id}: - // 发送成功,可选记录日志 - // log.Printf("send skill success, userID: %d, skillID: %d", c.GetInfo().UserID, id) - case <-time.After(time.Second * 60): - // 通道满时的降级处理 - cool.Logger.Printf(context.Background(), "actionChan is full, failed to send Capture, userID: %d ", - c.GetInfo().UserID) - } + f.submitAction(&action.UseItemAction{BaseAction: action.NewBaseAction(c.GetInfo().UserID), ItemID: id}) } @@ -151,20 +200,12 @@ func (f *FightC) UseItem(c common.PlayerI, cacthid, itemid uint32) { return } - -if f.Info.Mode== info.BattleMode.PET_MELEE { - go f.UseSkill(c, 0) - return -} - select { - case f.actionChan <- &action.UseItemAction{BaseAction: action.NewBaseAction(c.GetInfo().UserID), ItemID: itemid, CacthTime: cacthid}: - // 发送成功,可选记录日志 - // log.Printf("send skill success, userID: %d, skillID: %d", c.GetInfo().UserID, id) - case <-time.After(time.Second * 60): - // 通道满时的降级处理 - cool.Logger.Printf(context.Background(), "actionChan is full, failed to send UseItem, userID: %d, skillID: %d", - c.GetInfo().UserID, cacthid) + + if f.Info.Mode == info.BattleMode.PET_MELEE { + go f.UseSkill(c, 0) + return } + f.submitAction(&action.UseItemAction{BaseAction: action.NewBaseAction(c.GetInfo().UserID), ItemID: itemid, CacthTime: cacthid}) } // ReadyFight 处理玩家战斗准备逻辑,当满足条件时启动战斗循环 diff --git a/logic/service/fight/fightc.go b/logic/service/fight/fightc.go index fdbdd5ddf..ae83d027d 100644 --- a/logic/service/fight/fightc.go +++ b/logic/service/fight/fightc.go @@ -346,13 +346,6 @@ func (f *FightC) enterturn(firstAttack, secondAttack *action.SelectSkillAction) return } - if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC { - // println("AI出手") - //go f.Our.GetAction() - go f.Opp.GetAction() - - } - } func (f *FightC) TURNOVER(cur *input.Input) { diff --git a/logic/service/fight/input.go b/logic/service/fight/input.go index a88e434ff..6da008648 100644 --- a/logic/service/fight/input.go +++ b/logic/service/fight/input.go @@ -29,9 +29,12 @@ type FightC struct { Opp *input.Input //对手ID Switch map[uint32]*action.ActiveSwitchAction - startl sync.Once - StartTime time.Time - actionChan chan action.BattleActionI // 所有操作统一从这里进入 + startl sync.Once + StartTime time.Time + actionMu sync.Mutex + actionNotify chan struct{} + acceptActions bool + pendingActions []action.BattleActionI // 待处理动作队列,同一玩家最多保留两段动作 quit chan struct{} over chan struct{} diff --git a/logic/service/fight/loop.go b/logic/service/fight/loop.go index b950b5990..d7d569bfd 100644 --- a/logic/service/fight/loop.go +++ b/logic/service/fight/loop.go @@ -43,16 +43,11 @@ func (f *FightC) battleLoop() { } }() - f.actionChan = make(chan action.BattleActionI, 10) //fmt.Println("战斗开始精灵", f.Our.Player.GetInfo().PetList[0].CatchTime) ourID := f.Our.Player.GetInfo().UserID oppID := f.Opp.Player.GetInfo().UserID //fmt.Println("开始收集玩家动作", waitr) - if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC { - go f.Opp.GetAction() - - } for !f.closefight { f.Round++ @@ -152,6 +147,12 @@ func (f *FightC) battleLoop() { // 收集玩家动作(含超时判定) func (f *FightC) collectPlayerActions(ourID, oppID uint32) map[uint32]action.BattleActionI { actions := make(map[uint32]action.BattleActionI) + f.openActionWindow() + defer f.closeActionWindow() + + if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC { + go f.Opp.GetAction() + } waitr := time.Duration(f.waittime)*time.Millisecond*10 + 30*time.Second @@ -163,11 +164,8 @@ func (f *FightC) collectPlayerActions(ourID, oppID uint32) map[uint32]action.Bat f.closefight = true return actions - case paction, ok := <-f.actionChan: - if !ok { - f.closefight = true - return actions - } + case <-f.actionNotify: + paction := f.nextAction() if paction == nil { continue } diff --git a/logic/service/fight/new.go b/logic/service/fight/new.go index cdbb6c06c..973b866f7 100644 --- a/logic/service/fight/new.go +++ b/logic/service/fight/new.go @@ -23,6 +23,8 @@ func NewFight(p1, p2 common.PlayerI, b1, b2 []model.PetInfo, fn func(model.Fight f.callback = fn //战斗结束的回调 f.quit = make(chan struct{}) f.over = make(chan struct{}) + f.actionNotify = make(chan struct{}, 1) + f.pendingActions = make([]action.BattleActionI, 0, 4) f.StartTime = time.Now() f.Info = p1.Getfightinfo() diff --git a/logic/service/player/arena_flag.go b/logic/service/player/arena_flag.go new file mode 100644 index 000000000..522d127c1 --- /dev/null +++ b/logic/service/player/arena_flag.go @@ -0,0 +1,59 @@ +package player + +import "sync/atomic" + +const ( + ArenaFlagNoSwitchPet uint32 = 1 << iota + ArenaFlagNoHeal + ArenaFlagNoPVP +) + +const arenaHostFlagMask = ArenaFlagNoSwitchPet | ArenaFlagNoHeal | ArenaFlagNoPVP + +func (p *Player) addArenaFlag(mask uint32) { + for { + current := atomic.LoadUint32(&p.ArenaFlags) + next := current | mask + if current == next || atomic.CompareAndSwapUint32(&p.ArenaFlags, current, next) { + return + } + } +} + +func (p *Player) clearArenaFlag(mask uint32) { + for { + current := atomic.LoadUint32(&p.ArenaFlags) + next := current &^ mask + if current == next || atomic.CompareAndSwapUint32(&p.ArenaFlags, current, next) { + return + } + } +} + +func (p *Player) HasArenaFlag(mask uint32) bool { + return atomic.LoadUint32(&p.ArenaFlags)&mask != 0 +} + +func (p *Player) SetArenaHostFlags() { + p.addArenaFlag(arenaHostFlagMask) +} + +func (p *Player) ClearArenaHostFlags() { + p.clearArenaFlag(arenaHostFlagMask) +} + +func (p *Player) IsArenaHost() bool { + return p.HasArenaFlag(arenaHostFlagMask) +} + +func (p *Player) IsArenaSwitchLocked() bool { + return p.HasArenaFlag(ArenaFlagNoSwitchPet) +} + +func (p *Player) IsArenaHealLocked() bool { + return p.HasArenaFlag(ArenaFlagNoHeal) +} + +func (p *Player) IsArenaPVPLocked() bool { + return p.HasArenaFlag(ArenaFlagNoPVP) +} diff --git a/logic/service/player/fight.go b/logic/service/player/fight.go index 07d6f4e54..070c63ae9 100644 --- a/logic/service/player/fight.go +++ b/logic/service/player/fight.go @@ -13,7 +13,7 @@ func (p *Player) JoinFight(handler func(p common.PlayerI) bool) errorcode.ErrorC if p.CanFight() != 0 { return r } - if p.GetSpace().Owner.UserID == p.Info.UserID { + if p.IsArenaPVPLocked() { return errorcode.ErrorCodes.ErrSystemError } diff --git a/logic/service/player/player.go b/logic/service/player/player.go index 338be5d64..ac9d8668d 100644 --- a/logic/service/player/player.go +++ b/logic/service/player/player.go @@ -114,6 +114,8 @@ type Player struct { Canmon uint32 // 可以刷怪 CurDark uint32 Hash uint32 + + ArenaFlags uint32 } type OgrePet struct { diff --git a/logic/service/space/arena.go b/logic/service/space/arena.go index ecfc45b78..6219ee1ab 100644 --- a/logic/service/space/arena.go +++ b/logic/service/space/arena.go @@ -5,6 +5,23 @@ import ( "sync/atomic" ) +type arenaFlagPlayer interface { + SetArenaHostFlags() + ClearArenaHostFlags() +} + +func syncArenaHostFlags(player common.PlayerI, active bool) { + holder, ok := player.(arenaFlagPlayer) + if !ok { + return + } + if active { + holder.SetArenaHostFlags() + return + } + holder.ClearArenaHostFlags() +} + type ARENA struct { ARENA_Player common.PlayerI `struc:"skip"` Flag uint32 // 0=清除ArenaInfo(flag为0时其他字段全为空) 1=站上擂台的信息 2=挑战中的信息 @@ -15,6 +32,7 @@ type ARENA struct { } func (t *ARENA) Reset() { + syncArenaHostFlags(t.ARENA_Player, false) t.Flag = 0 t.UserID = 0 @@ -23,21 +41,22 @@ func (t *ARENA) Reset() { t.ChallengerID = 0 t.ARENA_Player = nil - } func (t *ARENA) Set(c common.PlayerI) bool { + if t.ARENA_Player != nil && t.ARENA_Player.GetInfo().UserID != c.GetInfo().UserID { + syncArenaHostFlags(t.ARENA_Player, false) + } if c.GetInfo().UserID == atomic.LoadUint32(&t.UserID) { - t.HostWins += 1 //连胜+1 - } else { t.HostWins = 0 //连胜重置 t.UserID = c.GetInfo().UserID //添加用户ID t.Nick = c.GetInfo().Nick - t.ARENA_Player = c //添加用户 } + t.ARENA_Player = c //添加用户 + syncArenaHostFlags(c, true) atomic.StoreUint32(&t.Flag, 1)