mirror of
https://github.com/FlourishingWorld/hk4e.git
synced 2026-02-04 14:22:26 +08:00
301 lines
7.5 KiB
Go
301 lines
7.5 KiB
Go
package handle
|
|
|
|
import (
|
|
"math"
|
|
"time"
|
|
|
|
"hk4e/common/constant"
|
|
"hk4e/common/mq"
|
|
"hk4e/gate/kcp"
|
|
"hk4e/gdconf"
|
|
"hk4e/node/api"
|
|
"hk4e/pkg/logger"
|
|
"hk4e/protocol/cmd"
|
|
"hk4e/protocol/proto"
|
|
|
|
pb "google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
const (
|
|
MoveVectorCacheNum = 10
|
|
MaxMoveSpeed = 100.0
|
|
JumpDistance = 500.0
|
|
PointDistance = 10.0
|
|
AttackCountLimitEntitySec = 10
|
|
KickCheatPlayer = false
|
|
)
|
|
|
|
type MoveVector struct {
|
|
pos *proto.Vector
|
|
time int64
|
|
}
|
|
|
|
type AttackEntity struct {
|
|
attackStartTime uint64
|
|
attackCount uint32
|
|
}
|
|
|
|
type AnticheatContext struct {
|
|
sceneId uint32
|
|
moveVectorList []*MoveVector
|
|
attackEntityMap map[uint32]*AttackEntity
|
|
}
|
|
|
|
func (a *AnticheatContext) Move(pos *proto.Vector) bool {
|
|
now := time.Now().UnixMilli()
|
|
if len(a.moveVectorList) > 0 {
|
|
lastMoveVector := a.moveVectorList[len(a.moveVectorList)-1]
|
|
if now-lastMoveVector.time < 1000 {
|
|
return true
|
|
}
|
|
distance := GetDistance(pos, lastMoveVector.pos)
|
|
if distance > JumpDistance {
|
|
// 瞬时变化太大 判断是否为传送
|
|
scenePointMap := gdconf.GetScenePointMapBySceneId(int32(a.sceneId))
|
|
if scenePointMap == nil {
|
|
return true
|
|
}
|
|
isJump := true
|
|
for _, pointData := range scenePointMap {
|
|
if pointData.TranPos == nil {
|
|
continue
|
|
}
|
|
d := GetDistance(pos, &proto.Vector{
|
|
X: float32(pointData.TranPos.X),
|
|
Y: float32(pointData.TranPos.Y),
|
|
Z: float32(pointData.TranPos.Z),
|
|
})
|
|
if d < PointDistance {
|
|
isJump = false
|
|
break
|
|
}
|
|
}
|
|
if isJump {
|
|
return false
|
|
} else {
|
|
a.moveVectorList = make([]*MoveVector, 0)
|
|
}
|
|
}
|
|
}
|
|
a.moveVectorList = append(a.moveVectorList, &MoveVector{
|
|
pos: pos,
|
|
time: now,
|
|
})
|
|
if len(a.moveVectorList) > MoveVectorCacheNum {
|
|
a.moveVectorList = a.moveVectorList[len(a.moveVectorList)-MoveVectorCacheNum:]
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (a *AnticheatContext) GetMoveSpeed() float32 {
|
|
avgMoveSpeed := float32(0.0)
|
|
if len(a.moveVectorList) < MoveVectorCacheNum {
|
|
return avgMoveSpeed
|
|
}
|
|
for index := range a.moveVectorList {
|
|
if index+1 >= len(a.moveVectorList) {
|
|
break
|
|
}
|
|
nextMoveVector := a.moveVectorList[index+1]
|
|
beforeMoveVector := a.moveVectorList[index]
|
|
dx := GetDistance(nextMoveVector.pos, beforeMoveVector.pos)
|
|
dt := float32(nextMoveVector.time-beforeMoveVector.time) / 1000.0
|
|
avgMoveSpeed += dx / dt
|
|
}
|
|
avgMoveSpeed /= float32(len(a.moveVectorList))
|
|
return avgMoveSpeed
|
|
}
|
|
|
|
func (a *AnticheatContext) Attack(defEntityId uint32) bool {
|
|
now := uint64(time.Now().UnixMilli())
|
|
attackEntity, exist := a.attackEntityMap[defEntityId]
|
|
if !exist {
|
|
attackEntity = &AttackEntity{
|
|
attackStartTime: now,
|
|
attackCount: 0,
|
|
}
|
|
a.attackEntityMap[defEntityId] = attackEntity
|
|
}
|
|
attackEntity.attackCount++
|
|
if attackEntity.attackCount > AttackCountLimitEntitySec {
|
|
if now-attackEntity.attackStartTime < 1000 {
|
|
return false
|
|
} else {
|
|
attackEntity.attackStartTime = now
|
|
attackEntity.attackCount = 0
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func NewAnticheatContext() *AnticheatContext {
|
|
r := &AnticheatContext{
|
|
sceneId: 0,
|
|
moveVectorList: make([]*MoveVector, 0),
|
|
attackEntityMap: make(map[uint32]*AttackEntity),
|
|
}
|
|
return r
|
|
}
|
|
|
|
type Handle struct {
|
|
messageQueue *mq.MessageQueue
|
|
playerAcCtxMap map[uint32]*AnticheatContext
|
|
}
|
|
|
|
func (h *Handle) AddPlayerAcCtx(userId uint32) {
|
|
h.playerAcCtxMap[userId] = NewAnticheatContext()
|
|
}
|
|
|
|
func (h *Handle) DelPlayerAcCtx(userId uint32) {
|
|
delete(h.playerAcCtxMap, userId)
|
|
}
|
|
|
|
func (h *Handle) GetPlayerAcCtx(userId uint32) *AnticheatContext {
|
|
return h.playerAcCtxMap[userId]
|
|
}
|
|
|
|
func NewHandle(messageQueue *mq.MessageQueue) (r *Handle) {
|
|
r = new(Handle)
|
|
r.messageQueue = messageQueue
|
|
r.playerAcCtxMap = make(map[uint32]*AnticheatContext)
|
|
go r.run()
|
|
return r
|
|
}
|
|
|
|
func (h *Handle) run() {
|
|
logger.Info("start handle")
|
|
for {
|
|
netMsg := <-h.messageQueue.GetNetMsg()
|
|
switch netMsg.MsgType {
|
|
case mq.MsgTypeGame:
|
|
if netMsg.OriginServerType != api.GATE {
|
|
continue
|
|
}
|
|
if netMsg.EventId != mq.NormalMsg {
|
|
continue
|
|
}
|
|
gameMsg := netMsg.GameMsg
|
|
switch gameMsg.CmdId {
|
|
case cmd.CombatInvocationsNotify:
|
|
h.CombatInvocationsNotify(gameMsg.UserId, netMsg.OriginServerAppId, gameMsg.PayloadMessage)
|
|
case cmd.ToTheMoonEnterSceneReq:
|
|
h.ToTheMoonEnterSceneReq(gameMsg.UserId, netMsg.OriginServerAppId, gameMsg.PayloadMessage)
|
|
}
|
|
case mq.MsgTypeServer:
|
|
serverMsg := netMsg.ServerMsg
|
|
switch netMsg.EventId {
|
|
case mq.ServerUserOnlineStateChangeNotify:
|
|
logger.Info("player online state change, state: %v, uid: %v", serverMsg.IsOnline, serverMsg.UserId)
|
|
if serverMsg.IsOnline {
|
|
h.AddPlayerAcCtx(serverMsg.UserId)
|
|
} else {
|
|
h.DelPlayerAcCtx(serverMsg.UserId)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *Handle) CombatInvocationsNotify(userId uint32, gateAppId string, payloadMsg pb.Message) {
|
|
req := payloadMsg.(*proto.CombatInvocationsNotify)
|
|
ctx := h.GetPlayerAcCtx(userId)
|
|
if ctx == nil {
|
|
logger.Error("get player anticheat context is nil, uid: %v", userId)
|
|
return
|
|
}
|
|
for _, entry := range req.InvokeList {
|
|
switch entry.ArgumentType {
|
|
case proto.CombatTypeArgument_ENTITY_MOVE:
|
|
entityMoveInfo := new(proto.EntityMoveInfo)
|
|
err := pb.Unmarshal(entry.CombatData, entityMoveInfo)
|
|
if err != nil {
|
|
logger.Error("parse EntityMoveInfo error: %v, uid: %v", err, userId)
|
|
continue
|
|
}
|
|
if GetEntityType(entityMoveInfo.EntityId) != constant.ENTITY_TYPE_AVATAR {
|
|
continue
|
|
}
|
|
if entityMoveInfo.MotionInfo == nil {
|
|
continue
|
|
}
|
|
motionInfo := entityMoveInfo.MotionInfo
|
|
if motionInfo.Pos == nil {
|
|
continue
|
|
}
|
|
// 玩家超速移动检测
|
|
if ctx.sceneId != 3 {
|
|
continue
|
|
}
|
|
ok := ctx.Move(motionInfo.Pos)
|
|
if !ok {
|
|
logger.Warn("player move jump, pos: %v, uid: %v", motionInfo.Pos, userId)
|
|
h.KickPlayer(userId, gateAppId)
|
|
continue
|
|
}
|
|
moveSpeed := ctx.GetMoveSpeed()
|
|
if moveSpeed > MaxMoveSpeed {
|
|
logger.Warn("player move overspeed, speed: %v, uid: %v", moveSpeed, userId)
|
|
h.KickPlayer(userId, gateAppId)
|
|
continue
|
|
}
|
|
case proto.CombatTypeArgument_COMBAT_EVT_BEING_HIT:
|
|
evtBeingHitInfo := new(proto.EvtBeingHitInfo)
|
|
err := pb.Unmarshal(entry.CombatData, evtBeingHitInfo)
|
|
if err != nil {
|
|
logger.Error("parse EvtBeingHitInfo error: %v, uid: %v", err, userId)
|
|
continue
|
|
}
|
|
attackResult := evtBeingHitInfo.AttackResult
|
|
if attackResult == nil {
|
|
continue
|
|
}
|
|
if GetEntityType(attackResult.DefenseId) != constant.ENTITY_TYPE_MONSTER {
|
|
continue
|
|
}
|
|
ok := ctx.Attack(attackResult.DefenseId)
|
|
if !ok {
|
|
logger.Warn("player attack monster feq too high, uid: %v", userId)
|
|
h.KickPlayer(userId, gateAppId)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *Handle) ToTheMoonEnterSceneReq(userId uint32, gateAppId string, payloadMsg pb.Message) {
|
|
req := payloadMsg.(*proto.ToTheMoonEnterSceneReq)
|
|
ctx := h.GetPlayerAcCtx(userId)
|
|
if ctx == nil {
|
|
logger.Error("get player anticheat context is nil, uid: %v", userId)
|
|
return
|
|
}
|
|
ctx.sceneId = req.SceneId
|
|
logger.Info("player enter scene: %v, uid: %v", req.SceneId, userId)
|
|
}
|
|
|
|
func GetEntityType(entityId uint32) int {
|
|
return int(entityId >> 24)
|
|
}
|
|
|
|
func GetDistance(v1 *proto.Vector, v2 *proto.Vector) float32 {
|
|
return float32(math.Sqrt(
|
|
float64((v1.X-v2.X)*(v1.X-v2.X)) +
|
|
float64((v1.Y-v2.Y)*(v1.Y-v2.Y)) +
|
|
float64((v1.Z-v2.Z)*(v1.Z-v2.Z)),
|
|
))
|
|
}
|
|
|
|
func (h *Handle) KickPlayer(userId uint32, gateAppId string) {
|
|
if !KickCheatPlayer {
|
|
return
|
|
}
|
|
h.messageQueue.SendToGate(gateAppId, &mq.NetMsg{
|
|
MsgType: mq.MsgTypeConnCtrl,
|
|
EventId: mq.KickPlayerNotify,
|
|
ConnCtrlMsg: &mq.ConnCtrlMsg{
|
|
KickUserId: userId,
|
|
KickReason: kcp.EnetServerKillClient,
|
|
},
|
|
})
|
|
}
|