mirror of
https://github.com/FlourishingWorld/hk4e.git
synced 2026-02-04 14:22:26 +08:00
322 lines
9.8 KiB
Go
322 lines
9.8 KiB
Go
package engine
|
|
|
|
import (
|
|
"reflect"
|
|
"time"
|
|
|
|
"hk4e/common/config"
|
|
"hk4e/common/constant"
|
|
"hk4e/common/mq"
|
|
"hk4e/common/utils"
|
|
"hk4e/gate/client_proto"
|
|
"hk4e/pkg/logger"
|
|
"hk4e/protocol/cmd"
|
|
"hk4e/protocol/proto"
|
|
|
|
pb "google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
type FightEngine struct {
|
|
messageQueue *mq.MessageQueue
|
|
}
|
|
|
|
func NewFightEngine(messageQueue *mq.MessageQueue) (r *FightEngine) {
|
|
r = new(FightEngine)
|
|
r.messageQueue = messageQueue
|
|
initClientCmdProtoMap()
|
|
go r.fightHandle()
|
|
return r
|
|
}
|
|
|
|
func (f *FightEngine) fightHandle() {
|
|
fightRoutineMsgChanMap := make(map[uint32]chan *mq.NetMsg)
|
|
fightRoutineCloseChanMap := make(map[uint32]chan bool)
|
|
userIdFightRoutineIdMap := make(map[uint32]uint32)
|
|
for {
|
|
netMsg := <-f.messageQueue.GetNetMsg()
|
|
logger.Debug("recv net msg, netMsg: %v", netMsg)
|
|
switch netMsg.MsgType {
|
|
case mq.MsgTypeGame:
|
|
gameMsg := netMsg.GameMsg
|
|
if netMsg.EventId != mq.NormalMsg {
|
|
continue
|
|
}
|
|
logger.Debug("recv game msg, gameMsg: %v", gameMsg)
|
|
fightRoutineId, exist := userIdFightRoutineIdMap[gameMsg.UserId]
|
|
if !exist {
|
|
logger.Error("could not found fight routine id by uid: %v", gameMsg.UserId)
|
|
continue
|
|
}
|
|
fightRoutineMsgChan, exist := fightRoutineMsgChanMap[fightRoutineId]
|
|
if !exist {
|
|
logger.Error("could not found fight routine msg chan by fight routine id: %v", fightRoutineId)
|
|
continue
|
|
}
|
|
fightRoutineMsgChan <- netMsg
|
|
case mq.MsgTypeFight:
|
|
fightMsg := netMsg.FightMsg
|
|
logger.Debug("recv fight msg, fightMsg: %v", fightMsg)
|
|
switch netMsg.EventId {
|
|
case mq.AddFightRoutine:
|
|
fightRoutineMsgChan := make(chan *mq.NetMsg, 1000)
|
|
fightRoutineMsgChanMap[fightMsg.FightRoutineId] = fightRoutineMsgChan
|
|
fightRoutineCloseChan := make(chan bool, 1)
|
|
fightRoutineCloseChanMap[fightMsg.FightRoutineId] = fightRoutineCloseChan
|
|
go runFightRoutine(fightMsg.FightRoutineId, fightMsg.GateServerAppId, fightRoutineMsgChan, fightRoutineCloseChan, f.messageQueue)
|
|
case mq.DelFightRoutine:
|
|
fightRoutineCloseChan, exist := fightRoutineCloseChanMap[fightMsg.FightRoutineId]
|
|
if !exist {
|
|
logger.Error("could not found fight routine close chan by fight routine id: %v", fightMsg.FightRoutineId)
|
|
continue
|
|
}
|
|
fightRoutineCloseChan <- true
|
|
case mq.FightRoutineAddEntity:
|
|
if fightMsg.Uid != 0 {
|
|
userIdFightRoutineIdMap[fightMsg.Uid] = fightMsg.FightRoutineId
|
|
}
|
|
fightRoutineMsgChan, exist := fightRoutineMsgChanMap[fightMsg.FightRoutineId]
|
|
if !exist {
|
|
logger.Error("could not found fight routine msg chan by fight routine id: %v", fightMsg.FightRoutineId)
|
|
continue
|
|
}
|
|
fightRoutineMsgChan <- netMsg
|
|
case mq.FightRoutineDelEntity:
|
|
if fightMsg.Uid != 0 {
|
|
delete(userIdFightRoutineIdMap, fightMsg.Uid)
|
|
}
|
|
fightRoutineMsgChan, exist := fightRoutineMsgChanMap[fightMsg.FightRoutineId]
|
|
if !exist {
|
|
logger.Error("could not found fight routine msg chan by fight routine id: %v", fightMsg.FightRoutineId)
|
|
continue
|
|
}
|
|
fightRoutineMsgChan <- netMsg
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// SendMsg 发送消息给客户端
|
|
func SendMsg(messageQueue *mq.MessageQueue, cmdId uint16, userId uint32, gateAppId string, payloadMsg pb.Message) {
|
|
if userId < 100000000 || payloadMsg == nil {
|
|
return
|
|
}
|
|
gameMsg := new(mq.GameMsg)
|
|
gameMsg.UserId = userId
|
|
gameMsg.CmdId = cmdId
|
|
gameMsg.ClientSeq = 0
|
|
// 在这里直接序列化成二进制数据 防止发送的消息内包含各种游戏数据指针 而造成并发读写的问题
|
|
payloadMessageData, err := pb.Marshal(payloadMsg)
|
|
if err != nil {
|
|
logger.Error("parse payload msg to bin error: %v", err)
|
|
return
|
|
}
|
|
gameMsg.PayloadMessageData = payloadMessageData
|
|
messageQueue.SendToGate(gateAppId, &mq.NetMsg{
|
|
MsgType: mq.MsgTypeGame,
|
|
EventId: mq.NormalMsg,
|
|
GameMsg: gameMsg,
|
|
})
|
|
}
|
|
|
|
type Entity struct {
|
|
entityId uint32
|
|
fightPropMap map[uint32]float32
|
|
uid uint32
|
|
avatarGuid uint64
|
|
}
|
|
|
|
// FightRoutine 战局例程
|
|
type FightRoutine struct {
|
|
messageQueue *mq.MessageQueue
|
|
entityMap map[uint32]*Entity
|
|
combatInvokeEntryList []*proto.CombatInvokeEntry
|
|
tickCount uint64
|
|
gateAppId string
|
|
}
|
|
|
|
func runFightRoutine(fightRoutineId uint32, gateAppId string, fightRoutineMsgChan chan *mq.NetMsg, fightRoutineCloseChan chan bool, messageQueue *mq.MessageQueue) {
|
|
f := new(FightRoutine)
|
|
f.messageQueue = messageQueue
|
|
f.entityMap = make(map[uint32]*Entity)
|
|
f.combatInvokeEntryList = make([]*proto.CombatInvokeEntry, 0)
|
|
f.tickCount = 0
|
|
f.gateAppId = gateAppId
|
|
logger.Debug("create fight routine, fightRoutineId: %v", fightRoutineId)
|
|
ticker := time.NewTicker(time.Millisecond * 10)
|
|
for {
|
|
select {
|
|
case netMsg := <-fightRoutineMsgChan:
|
|
switch netMsg.MsgType {
|
|
case mq.MsgTypeGame:
|
|
gameMsg := netMsg.GameMsg
|
|
f.attackHandle(gameMsg)
|
|
case mq.MsgTypeFight:
|
|
fightMsg := netMsg.FightMsg
|
|
switch netMsg.EventId {
|
|
case mq.FightRoutineAddEntity:
|
|
f.entityMap[fightMsg.EntityId] = &Entity{
|
|
entityId: fightMsg.EntityId,
|
|
fightPropMap: fightMsg.FightPropMap,
|
|
uid: fightMsg.Uid,
|
|
avatarGuid: fightMsg.AvatarGuid,
|
|
}
|
|
case mq.FightRoutineDelEntity:
|
|
delete(f.entityMap, fightMsg.EntityId)
|
|
}
|
|
}
|
|
case <-ticker.C:
|
|
f.onTick()
|
|
case <-fightRoutineCloseChan:
|
|
logger.Debug("destroy fight routine, fightRoutineId: %v", fightRoutineId)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (f *FightRoutine) onTick() {
|
|
f.tickCount++
|
|
now := time.Now().UnixMilli()
|
|
if f.tickCount%5 == 0 {
|
|
f.onTick50MilliSecond(now)
|
|
}
|
|
if f.tickCount%100 == 0 {
|
|
f.onTickSecond(now)
|
|
}
|
|
}
|
|
|
|
func (f *FightRoutine) onTick50MilliSecond(now int64) {
|
|
if len(f.combatInvokeEntryList) > 0 {
|
|
combatInvocationsNotifyAll := new(proto.CombatInvocationsNotify)
|
|
combatInvocationsNotifyAll.InvokeList = f.combatInvokeEntryList
|
|
for _, uid := range f.getAllPlayer(f.entityMap) {
|
|
SendMsg(f.messageQueue, cmd.CombatInvocationsNotify, uid, f.gateAppId, combatInvocationsNotifyAll)
|
|
}
|
|
f.combatInvokeEntryList = make([]*proto.CombatInvokeEntry, 0)
|
|
}
|
|
}
|
|
|
|
func (f *FightRoutine) onTickSecond(now int64) {
|
|
// 改面板
|
|
for _, entity := range f.entityMap {
|
|
if entity.uid == 0 {
|
|
continue
|
|
}
|
|
entity.fightPropMap[uint32(constant.FIGHT_PROP_CUR_ATTACK)] = 1000000
|
|
entity.fightPropMap[uint32(constant.FIGHT_PROP_CRITICAL)] = 1.0
|
|
avatarFightPropNotify := &proto.AvatarFightPropNotify{
|
|
AvatarGuid: entity.avatarGuid,
|
|
FightPropMap: entity.fightPropMap,
|
|
}
|
|
SendMsg(f.messageQueue, cmd.AvatarFightPropNotify, entity.uid, f.gateAppId, avatarFightPropNotify)
|
|
}
|
|
}
|
|
|
|
func (f *FightRoutine) attackHandle(gameMsg *mq.GameMsg) {
|
|
_ = gameMsg.UserId
|
|
cmdId := gameMsg.CmdId
|
|
_ = gameMsg.ClientSeq
|
|
payloadMsg := gameMsg.PayloadMessage
|
|
|
|
switch cmdId {
|
|
case cmd.CombatInvocationsNotify:
|
|
req := payloadMsg.(*proto.CombatInvocationsNotify)
|
|
for _, entry := range req.InvokeList {
|
|
if entry.ForwardType != proto.ForwardType_FORWARD_TO_ALL {
|
|
continue
|
|
}
|
|
if entry.ArgumentType != proto.CombatTypeArgument_COMBAT_EVT_BEING_HIT {
|
|
continue
|
|
}
|
|
hitInfo := new(proto.EvtBeingHitInfo)
|
|
if config.CONF.Hk4e.ClientProtoProxyEnable {
|
|
clientProtoObj := GetClientProtoObjByName("EvtBeingHitInfo")
|
|
if clientProtoObj == nil {
|
|
logger.Error("get client proto obj is nil")
|
|
continue
|
|
}
|
|
ok := utils.UnmarshalProtoObj(hitInfo, clientProtoObj, entry.CombatData)
|
|
if !ok {
|
|
continue
|
|
}
|
|
} else {
|
|
err := pb.Unmarshal(entry.CombatData, hitInfo)
|
|
if err != nil {
|
|
logger.Error("parse EvtBeingHitInfo error: %v", err)
|
|
continue
|
|
}
|
|
}
|
|
attackResult := hitInfo.AttackResult
|
|
if attackResult == nil {
|
|
logger.Error("attackResult is nil")
|
|
continue
|
|
}
|
|
// logger.Debug("run attack handler, attackResult: %v", attackResult)
|
|
target := f.entityMap[attackResult.DefenseId]
|
|
if target == nil {
|
|
logger.Error("could not found target, defense id: %v", attackResult.DefenseId)
|
|
continue
|
|
}
|
|
attackResult.Damage *= 100
|
|
damage := attackResult.Damage
|
|
attackerId := attackResult.AttackerId
|
|
_ = attackerId
|
|
currHp := float32(0)
|
|
if target.fightPropMap != nil {
|
|
currHp = target.fightPropMap[uint32(constant.FIGHT_PROP_CUR_HP)]
|
|
currHp -= damage
|
|
if currHp < 0 {
|
|
currHp = 0
|
|
}
|
|
target.fightPropMap[uint32(constant.FIGHT_PROP_CUR_HP)] = currHp
|
|
}
|
|
entityFightPropUpdateNotify := new(proto.EntityFightPropUpdateNotify)
|
|
entityFightPropUpdateNotify.EntityId = target.entityId
|
|
entityFightPropUpdateNotify.FightPropMap = make(map[uint32]float32)
|
|
entityFightPropUpdateNotify.FightPropMap[uint32(constant.FIGHT_PROP_CUR_HP)] = currHp
|
|
for _, uid := range f.getAllPlayer(f.entityMap) {
|
|
SendMsg(f.messageQueue, cmd.EntityFightPropUpdateNotify, uid, f.gateAppId, entityFightPropUpdateNotify)
|
|
}
|
|
combatData, err := pb.Marshal(hitInfo)
|
|
if err != nil {
|
|
logger.Error("create combat invocations entity hit info error: %v", err)
|
|
}
|
|
entry.CombatData = combatData
|
|
f.combatInvokeEntryList = append(f.combatInvokeEntryList, entry)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (f *FightRoutine) getAllPlayer(entityMap map[uint32]*Entity) []uint32 {
|
|
uidMap := make(map[uint32]bool)
|
|
for _, entity := range entityMap {
|
|
if entity.uid != 0 {
|
|
uidMap[entity.uid] = true
|
|
}
|
|
}
|
|
uidList := make([]uint32, 0)
|
|
for uid := range uidMap {
|
|
uidList = append(uidList, uid)
|
|
}
|
|
return uidList
|
|
}
|
|
|
|
var ClientCmdProtoMap *client_proto.ClientCmdProtoMap
|
|
|
|
func initClientCmdProtoMap() {
|
|
if config.CONF.Hk4e.ClientProtoProxyEnable {
|
|
ClientCmdProtoMap = client_proto.NewClientCmdProtoMap()
|
|
}
|
|
}
|
|
|
|
func GetClientProtoObjByName(protoObjName string) pb.Message {
|
|
fn := ClientCmdProtoMap.RefValue.MethodByName("GetClientProtoObjByName")
|
|
ret := fn.Call([]reflect.Value{reflect.ValueOf(protoObjName)})
|
|
obj := ret[0].Interface()
|
|
if obj == nil {
|
|
logger.Error("try to get a not exist proto obj, protoObjName: %v", protoObjName)
|
|
return nil
|
|
}
|
|
clientProtoObj := obj.(pb.Message)
|
|
return clientProtoObj
|
|
}
|