refactor: 重构战斗系统动作提交和竞技场锁定逻辑
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed

This commit is contained in:
xinian
2026-04-02 23:05:18 +08:00
committed by cnb
parent f221b299cd
commit 218e23ff81
13 changed files with 194 additions and 77 deletions

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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 处理玩家战斗准备逻辑,当满足条件时启动战斗循环

View File

@@ -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) {

View File

@@ -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{}

View File

@@ -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
}

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -114,6 +114,8 @@ type Player struct {
Canmon uint32 // 可以刷怪
CurDark uint32
Hash uint32
ArenaFlags uint32
}
type OgrePet struct {

View File

@@ -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=清除ArenaInfoflag为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)