vRp.CD2g_test/game/fight/turn_based.go

198 lines
6.0 KiB
Go

package fight
import (
"github.com/kercylan98/minotaur/utils/generic"
"sync"
"time"
)
const (
signalFinish = 1 + iota // 操作结束信号
signalRefresh // 刷新操作超时时间信号
)
type signal struct {
sign byte
data any
}
// NewTurnBased 创建一个新的回合制
// - calcNextTurnDuration 将返回下一次行动时间间隔,适用于按照速度计算下一次行动时间间隔的情况。当返回 0 时,将使用默认的行动超时时间
func NewTurnBased[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]](calcNextTurnDuration func(Camp, Entity) time.Duration) *TurnBased[CampID, EntityID, Camp, Entity] {
tb := &TurnBased[CampID, EntityID, Camp, Entity]{
turnBasedEvents: &turnBasedEvents[CampID, EntityID, Camp, Entity]{},
campRel: make(map[EntityID]Camp),
calcNextTurnDuration: calcNextTurnDuration,
actionTimeoutHandler: func(camp Camp, entity Entity) time.Duration {
return 0
},
}
tb.controller = &TurnBasedController[CampID, EntityID, Camp, Entity]{tb: tb}
return tb
}
// TurnBased 回合制
type TurnBased[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] struct {
*turnBasedEvents[CampID, EntityID, Camp, Entity]
controller *TurnBasedController[CampID, EntityID, Camp, Entity] // 控制器
ticker *time.Ticker // 计时器
actionWaitTicker *time.Ticker // 行动等待计时器
actioning bool // 是否正在行动
actionMutex sync.RWMutex // 行动锁
entities []Entity // 所有阵容实体顺序
campRel map[EntityID]Camp // 实体与阵营的关系
calcNextTurnDuration func(Camp, Entity) time.Duration // 下一次行动时间间隔
actionTimeoutHandler func(Camp, Entity) time.Duration // 行动超时时间
signal chan signal // 信号
round int // 当前回合数
currCamp Camp // 当前操作阵营
currEntity Entity // 当前操作实体
currActionTimeout time.Duration // 当前行动超时时间
currStart time.Time // 当前回合开始时间
closeMutex sync.RWMutex // 关闭锁
closed bool
}
// Close 关闭回合制
func (slf *TurnBased[CampID, EntityID, Camp, Entity]) Close() {
slf.closeMutex.Lock()
defer slf.closeMutex.Unlock()
slf.closed = true
}
// AddCamp 添加阵营
func (slf *TurnBased[CampID, EntityID, Camp, Entity]) AddCamp(camp Camp, entity Entity, entities ...Entity) {
for _, e := range append([]Entity{entity}, entities...) {
slf.entities = append(slf.entities, e)
slf.campRel[e.GetId()] = camp
}
}
// SetActionTimeout 设置行动超时时间处理函数
// - 默认情况下行动超时时间函数将始终返回 0
func (slf *TurnBased[CampID, EntityID, Camp, Entity]) SetActionTimeout(actionTimeoutHandler func(Camp, Entity) time.Duration) {
if actionTimeoutHandler == nil {
panic("actionTimeoutHandler can not be nil")
}
slf.actionTimeoutHandler = actionTimeoutHandler
}
// Run 运行
func (slf *TurnBased[CampID, EntityID, Camp, Entity]) Run() {
slf.round = 1
slf.signal = make(chan signal, 1)
var actionDuration = make(map[EntityID]time.Duration)
var actionSubmit = func() {
slf.actionMutex.Lock()
slf.actioning = false
if slf.actionWaitTicker != nil {
slf.actionWaitTicker.Stop()
}
slf.actionMutex.Unlock()
}
for {
slf.closeMutex.RLock()
if slf.closed {
slf.closeMutex.RUnlock()
break
}
slf.closeMutex.RUnlock()
var minDuration *time.Duration
var delay time.Duration
for _, entity := range slf.entities {
camp := slf.campRel[entity.GetId()]
next := slf.calcNextTurnDuration(camp, entity)
accumulate := next + actionDuration[entity.GetId()]
if minDuration == nil || accumulate < *minDuration {
minDuration = &accumulate
slf.currEntity = entity
slf.currCamp = camp
delay = next
}
}
if *minDuration == 0 {
*minDuration = 1 // 防止永远是第一对象行动
}
actionDuration[slf.currEntity.GetId()] = *minDuration
if len(actionDuration) == len(slf.entities) {
for key := range actionDuration {
delete(actionDuration, key)
}
}
if delay > 0 {
if slf.ticker == nil {
slf.ticker = time.NewTicker(delay)
} else {
slf.ticker.Reset(delay)
}
<-slf.ticker.C
}
// 进入回合操作阶段
slf.currActionTimeout = slf.actionTimeoutHandler(slf.currCamp, slf.currEntity)
slf.currStart = time.Now()
slf.actionMutex.Lock()
slf.actioning = true
slf.actionMutex.Unlock()
slf.OnTurnBasedEntitySwitchEvent(slf.controller)
if slf.actionWaitTicker == nil {
slf.actionWaitTicker = time.NewTicker(slf.currActionTimeout)
} else {
slf.actionWaitTicker.Reset(slf.currActionTimeout)
}
breakListen:
for {
wait:
select {
case <-slf.actionWaitTicker.C:
actionSubmit()
slf.OnTurnBasedEntityActionTimeoutEvent(slf.controller)
break breakListen
case sign := <-slf.signal:
switch sign.sign {
case signalFinish:
actionSubmit()
slf.OnTurnBasedEntityActionFinishEvent(slf.controller)
break breakListen
case signalRefresh:
slf.actionWaitTicker.Reset(sign.data.(time.Duration))
goto wait
}
}
}
slf.OnTurnBasedEntityActionSubmitEvent(slf.controller)
if len(actionDuration) == 0 {
slf.round++
}
slf.closeMutex.Lock()
if slf.closed {
if len(actionDuration) == 0 {
slf.round--
}
if slf.ticker != nil {
slf.ticker.Stop()
slf.ticker = nil
}
if slf.actionWaitTicker != nil {
slf.actionWaitTicker.Stop()
slf.actionWaitTicker = nil
}
if slf.signal != nil {
close(slf.signal)
slf.signal = nil
}
slf.closeMutex.Unlock()
break
} else if len(actionDuration) == 0 {
slf.OnTurnBasedRoundChangeEvent(slf.controller)
}
slf.closeMutex.Unlock()
}
}