refactor: 重构战斗系统动作提交和竞技场锁定逻辑
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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 处理玩家战斗准备逻辑,当满足条件时启动战斗循环
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
59
logic/service/player/arena_flag.go
Normal file
59
logic/service/player/arena_flag.go
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -114,6 +114,8 @@ type Player struct {
|
||||
Canmon uint32 // 可以刷怪
|
||||
CurDark uint32
|
||||
Hash uint32
|
||||
|
||||
ArenaFlags uint32
|
||||
}
|
||||
|
||||
type OgrePet struct {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user