mirror of
https://github.com/FlourishingWorld/hk4e.git
synced 2026-02-04 15:52:27 +08:00
475 lines
14 KiB
Go
475 lines
14 KiB
Go
package game
|
|
|
|
import (
|
|
pb "google.golang.org/protobuf/proto"
|
|
"hk4e/common/utils/alg"
|
|
"hk4e/gs/constant"
|
|
"hk4e/gs/game/aoi"
|
|
"hk4e/gs/model"
|
|
"hk4e/logger"
|
|
"hk4e/protocol/cmd"
|
|
"hk4e/protocol/proto"
|
|
"math"
|
|
"time"
|
|
)
|
|
|
|
// 世界管理器
|
|
|
|
type WorldManager struct {
|
|
worldMap map[uint32]*World
|
|
snowflake *alg.SnowflakeWorker
|
|
worldStatic *WorldStatic
|
|
bigWorld *World
|
|
}
|
|
|
|
func NewWorldManager(snowflake *alg.SnowflakeWorker) (r *WorldManager) {
|
|
r = new(WorldManager)
|
|
r.worldMap = make(map[uint32]*World)
|
|
r.snowflake = snowflake
|
|
r.worldStatic = NewWorldStatic()
|
|
r.worldStatic.InitTerrain()
|
|
//r.worldStatic.Pathfinding()
|
|
//r.worldStatic.ConvPathVectorListToAiMoveVectorList()
|
|
return r
|
|
}
|
|
|
|
func (w *WorldManager) GetWorldByID(worldId uint32) *World {
|
|
return w.worldMap[worldId]
|
|
}
|
|
|
|
func (w *WorldManager) GetWorldMap() map[uint32]*World {
|
|
return w.worldMap
|
|
}
|
|
|
|
func (w *WorldManager) CreateWorld(owner *model.Player, multiplayer bool) *World {
|
|
worldId := uint32(w.snowflake.GenId())
|
|
world := &World{
|
|
id: worldId,
|
|
owner: owner,
|
|
playerMap: make(map[uint32]*model.Player),
|
|
sceneMap: make(map[uint32]*Scene),
|
|
entityIdCounter: 0,
|
|
worldLevel: 0,
|
|
multiplayer: multiplayer,
|
|
mpLevelEntityId: 0,
|
|
chatMsgList: make([]*proto.ChatInfo, 0),
|
|
// aoi划分
|
|
// TODO 为减少内存占用暂时去掉Y轴AOI格子划分 原来的Y轴格子数量为80
|
|
aoiManager: aoi.NewAoiManager(
|
|
-8000, 4000, 120,
|
|
-2000, 2000, 1,
|
|
-5500, 6500, 120,
|
|
),
|
|
}
|
|
if world.IsBigWorld() {
|
|
world.aoiManager = aoi.NewAoiManager(
|
|
-8000, 4000, 800,
|
|
-2000, 2000, 1,
|
|
-5500, 6500, 800,
|
|
)
|
|
}
|
|
world.mpLevelEntityId = world.GetNextWorldEntityId(constant.EntityIdTypeConst.MPLEVEL)
|
|
w.worldMap[worldId] = world
|
|
return world
|
|
}
|
|
|
|
func (w *WorldManager) DestroyWorld(worldId uint32) {
|
|
world := w.GetWorldByID(worldId)
|
|
for _, player := range world.playerMap {
|
|
world.RemovePlayer(player)
|
|
player.WorldId = 0
|
|
}
|
|
delete(w.worldMap, worldId)
|
|
}
|
|
|
|
func (w *WorldManager) GetBigWorld() *World {
|
|
return w.bigWorld
|
|
}
|
|
|
|
func (w *WorldManager) InitBigWorld(owner *model.Player) {
|
|
w.bigWorld = w.GetWorldByID(owner.WorldId)
|
|
w.bigWorld.multiplayer = true
|
|
}
|
|
|
|
type World struct {
|
|
id uint32
|
|
owner *model.Player
|
|
playerMap map[uint32]*model.Player
|
|
sceneMap map[uint32]*Scene
|
|
entityIdCounter uint32
|
|
worldLevel uint8
|
|
multiplayer bool
|
|
mpLevelEntityId uint32
|
|
chatMsgList []*proto.ChatInfo
|
|
aoiManager *aoi.AoiManager // 当前世界地图的aoi管理器
|
|
}
|
|
|
|
func (w *World) GetNextWorldEntityId(entityType uint16) uint32 {
|
|
w.entityIdCounter++
|
|
ret := (uint32(entityType) << 24) + w.entityIdCounter
|
|
return ret
|
|
}
|
|
|
|
func (w *World) AddPlayer(player *model.Player, sceneId uint32) {
|
|
player.PeerId = uint32(len(w.playerMap) + 1)
|
|
w.playerMap[player.PlayerID] = player
|
|
scene := w.GetSceneById(sceneId)
|
|
scene.AddPlayer(player)
|
|
}
|
|
|
|
func (w *World) RemovePlayer(player *model.Player) {
|
|
scene := w.sceneMap[player.SceneId]
|
|
scene.RemovePlayer(player)
|
|
delete(w.playerMap, player.PlayerID)
|
|
}
|
|
|
|
func (w *World) CreateScene(sceneId uint32) *Scene {
|
|
scene := &Scene{
|
|
id: sceneId,
|
|
world: w,
|
|
playerMap: make(map[uint32]*model.Player),
|
|
entityMap: make(map[uint32]*Entity),
|
|
playerTeamEntityMap: make(map[uint32]*PlayerTeamEntity),
|
|
gameTime: 18 * 60,
|
|
attackQueue: alg.NewRAQueue[*Attack](1000),
|
|
createTime: time.Now().UnixMilli(),
|
|
}
|
|
w.sceneMap[sceneId] = scene
|
|
return scene
|
|
}
|
|
|
|
func (w *World) GetSceneById(sceneId uint32) *Scene {
|
|
scene, exist := w.sceneMap[sceneId]
|
|
if !exist {
|
|
scene = w.CreateScene(sceneId)
|
|
}
|
|
return scene
|
|
}
|
|
|
|
func (w *World) AddChat(chatInfo *proto.ChatInfo) {
|
|
w.chatMsgList = append(w.chatMsgList, chatInfo)
|
|
}
|
|
|
|
func (w *World) GetChatList() []*proto.ChatInfo {
|
|
return w.chatMsgList
|
|
}
|
|
|
|
func (w *World) IsBigWorld() bool {
|
|
return w.owner.PlayerID == 1
|
|
}
|
|
|
|
type Scene struct {
|
|
id uint32
|
|
world *World
|
|
playerMap map[uint32]*model.Player
|
|
entityMap map[uint32]*Entity
|
|
playerTeamEntityMap map[uint32]*PlayerTeamEntity
|
|
gameTime uint32
|
|
attackQueue *alg.RAQueue[*Attack]
|
|
createTime int64
|
|
}
|
|
|
|
type AvatarEntity struct {
|
|
uid uint32
|
|
avatarId uint32
|
|
}
|
|
|
|
type MonsterEntity struct {
|
|
}
|
|
|
|
type GadgetEntity struct {
|
|
gatherId uint32
|
|
}
|
|
|
|
type Entity struct {
|
|
id uint32
|
|
scene *Scene
|
|
pos *model.Vector
|
|
rot *model.Vector
|
|
moveState uint16
|
|
lastMoveSceneTimeMs uint32
|
|
lastMoveReliableSeq uint32
|
|
fightProp map[uint32]float32
|
|
entityType uint32
|
|
level uint8
|
|
avatarEntity *AvatarEntity
|
|
monsterEntity *MonsterEntity
|
|
gadgetEntity *GadgetEntity
|
|
}
|
|
|
|
type PlayerTeamEntity struct {
|
|
teamEntityId uint32
|
|
avatarEntityMap map[uint32]uint32
|
|
weaponEntityMap map[uint64]uint32
|
|
}
|
|
|
|
type Attack struct {
|
|
combatInvokeEntry *proto.CombatInvokeEntry
|
|
uid uint32
|
|
}
|
|
|
|
func (s *Scene) ChangeGameTime(time uint32) {
|
|
s.gameTime = time % 1440
|
|
}
|
|
|
|
func (s *Scene) GetSceneTime() int64 {
|
|
now := time.Now().UnixMilli()
|
|
return now - s.createTime
|
|
}
|
|
|
|
func (s *Scene) GetPlayerTeamEntity(userId uint32) *PlayerTeamEntity {
|
|
return s.playerTeamEntityMap[userId]
|
|
}
|
|
|
|
func (s *Scene) CreatePlayerTeamEntity(player *model.Player) {
|
|
playerTeamEntity := &PlayerTeamEntity{
|
|
teamEntityId: s.world.GetNextWorldEntityId(constant.EntityIdTypeConst.TEAM),
|
|
avatarEntityMap: make(map[uint32]uint32),
|
|
weaponEntityMap: make(map[uint64]uint32),
|
|
}
|
|
s.playerTeamEntityMap[player.PlayerID] = playerTeamEntity
|
|
}
|
|
|
|
func (s *Scene) UpdatePlayerTeamEntity(player *model.Player) {
|
|
team := player.TeamConfig.GetActiveTeam()
|
|
playerTeamEntity := s.playerTeamEntityMap[player.PlayerID]
|
|
for _, avatarId := range team.AvatarIdList {
|
|
if avatarId == 0 {
|
|
break
|
|
}
|
|
avatar := player.AvatarMap[avatarId]
|
|
avatarEntityId, exist := playerTeamEntity.avatarEntityMap[avatarId]
|
|
if exist {
|
|
s.DestroyEntity(avatarEntityId)
|
|
}
|
|
playerTeamEntity.avatarEntityMap[avatarId] = s.CreateEntityAvatar(player, avatarId)
|
|
weaponEntityId, exist := playerTeamEntity.weaponEntityMap[avatar.EquipWeapon.WeaponId]
|
|
if exist {
|
|
s.DestroyEntity(weaponEntityId)
|
|
}
|
|
playerTeamEntity.weaponEntityMap[avatar.EquipWeapon.WeaponId] = s.CreateEntityWeapon()
|
|
}
|
|
}
|
|
|
|
func (s *Scene) AddPlayer(player *model.Player) {
|
|
s.playerMap[player.PlayerID] = player
|
|
s.CreatePlayerTeamEntity(player)
|
|
s.UpdatePlayerTeamEntity(player)
|
|
}
|
|
|
|
func (s *Scene) RemovePlayer(player *model.Player) {
|
|
playerTeamEntity := s.GetPlayerTeamEntity(player.PlayerID)
|
|
for _, avatarEntityId := range playerTeamEntity.avatarEntityMap {
|
|
s.DestroyEntity(avatarEntityId)
|
|
}
|
|
for _, weaponEntityId := range playerTeamEntity.weaponEntityMap {
|
|
s.DestroyEntity(weaponEntityId)
|
|
}
|
|
delete(s.playerTeamEntityMap, player.PlayerID)
|
|
delete(s.playerMap, player.PlayerID)
|
|
}
|
|
|
|
func (s *Scene) CreateEntityAvatar(player *model.Player, avatarId uint32) uint32 {
|
|
entityId := s.world.GetNextWorldEntityId(constant.EntityIdTypeConst.AVATAR)
|
|
entity := &Entity{
|
|
id: entityId,
|
|
scene: s,
|
|
pos: player.Pos,
|
|
rot: player.Rot,
|
|
moveState: uint16(proto.MotionState_MOTION_STATE_NONE),
|
|
lastMoveSceneTimeMs: 0,
|
|
lastMoveReliableSeq: 0,
|
|
fightProp: player.AvatarMap[avatarId].FightPropMap,
|
|
entityType: uint32(proto.ProtEntityType_PROT_ENTITY_TYPE_AVATAR),
|
|
level: player.AvatarMap[avatarId].Level,
|
|
avatarEntity: &AvatarEntity{
|
|
uid: player.PlayerID,
|
|
avatarId: avatarId,
|
|
},
|
|
}
|
|
s.entityMap[entity.id] = entity
|
|
if avatarId == player.TeamConfig.GetActiveAvatarId() {
|
|
s.world.aoiManager.AddEntityIdToGridByPos(entity.id, float32(entity.pos.X), float32(entity.pos.Y), float32(entity.pos.Z))
|
|
}
|
|
return entity.id
|
|
}
|
|
|
|
func (s *Scene) CreateEntityWeapon() uint32 {
|
|
entityId := s.world.GetNextWorldEntityId(constant.EntityIdTypeConst.WEAPON)
|
|
entity := &Entity{
|
|
id: entityId,
|
|
scene: s,
|
|
pos: new(model.Vector),
|
|
rot: new(model.Vector),
|
|
moveState: uint16(proto.MotionState_MOTION_STATE_NONE),
|
|
lastMoveSceneTimeMs: 0,
|
|
lastMoveReliableSeq: 0,
|
|
fightProp: nil,
|
|
entityType: uint32(proto.ProtEntityType_PROT_ENTITY_TYPE_WEAPON),
|
|
level: 0,
|
|
}
|
|
s.entityMap[entity.id] = entity
|
|
return entity.id
|
|
}
|
|
|
|
func (s *Scene) CreateEntityMonster(pos *model.Vector, level uint8, fightProp map[uint32]float32) uint32 {
|
|
entityId := s.world.GetNextWorldEntityId(constant.EntityIdTypeConst.MONSTER)
|
|
entity := &Entity{
|
|
id: entityId,
|
|
scene: s,
|
|
pos: pos,
|
|
rot: new(model.Vector),
|
|
moveState: uint16(proto.MotionState_MOTION_STATE_NONE),
|
|
lastMoveSceneTimeMs: 0,
|
|
lastMoveReliableSeq: 0,
|
|
fightProp: fightProp,
|
|
entityType: uint32(proto.ProtEntityType_PROT_ENTITY_TYPE_MONSTER),
|
|
level: level,
|
|
}
|
|
s.entityMap[entity.id] = entity
|
|
s.world.aoiManager.AddEntityIdToGridByPos(entity.id, float32(entity.pos.X), float32(entity.pos.Y), float32(entity.pos.Z))
|
|
return entity.id
|
|
}
|
|
|
|
func (s *Scene) CreateEntityGadget(pos *model.Vector, gatherId uint32) uint32 {
|
|
entityId := s.world.GetNextWorldEntityId(constant.EntityIdTypeConst.GADGET)
|
|
entity := &Entity{
|
|
id: entityId,
|
|
scene: s,
|
|
pos: pos,
|
|
rot: new(model.Vector),
|
|
moveState: uint16(proto.MotionState_MOTION_STATE_NONE),
|
|
lastMoveSceneTimeMs: 0,
|
|
lastMoveReliableSeq: 0,
|
|
fightProp: map[uint32]float32{
|
|
uint32(constant.FightPropertyConst.FIGHT_PROP_CUR_HP): math.MaxFloat32,
|
|
uint32(constant.FightPropertyConst.FIGHT_PROP_MAX_HP): math.MaxFloat32,
|
|
uint32(constant.FightPropertyConst.FIGHT_PROP_BASE_HP): float32(1),
|
|
},
|
|
entityType: uint32(proto.ProtEntityType_PROT_ENTITY_TYPE_GADGET),
|
|
level: 0,
|
|
gadgetEntity: &GadgetEntity{
|
|
gatherId: gatherId,
|
|
},
|
|
}
|
|
s.entityMap[entity.id] = entity
|
|
s.world.aoiManager.AddEntityIdToGridByPos(entity.id, float32(entity.pos.X), float32(entity.pos.Y), float32(entity.pos.Z))
|
|
return entity.id
|
|
}
|
|
|
|
func (s *Scene) DestroyEntity(entityId uint32) {
|
|
entity := s.GetEntity(entityId)
|
|
if entity == nil {
|
|
return
|
|
}
|
|
s.world.aoiManager.RemoveEntityIdFromGridByPos(entity.id, float32(entity.pos.X), float32(entity.pos.Y), float32(entity.pos.Z))
|
|
delete(s.entityMap, entityId)
|
|
}
|
|
|
|
func (s *Scene) GetEntity(entityId uint32) *Entity {
|
|
return s.entityMap[entityId]
|
|
}
|
|
|
|
// 伤害处理和转发
|
|
|
|
func (s *Scene) AddAttack(attack *Attack) {
|
|
s.attackQueue.EnQueue(attack)
|
|
}
|
|
|
|
func (s *Scene) AttackHandler(gameManager *GameManager) {
|
|
combatInvokeEntryListAll := make([]*proto.CombatInvokeEntry, 0)
|
|
combatInvokeEntryListOther := make(map[uint32][]*proto.CombatInvokeEntry)
|
|
combatInvokeEntryListHost := make([]*proto.CombatInvokeEntry, 0)
|
|
|
|
for s.attackQueue.Len() != 0 {
|
|
attack := s.attackQueue.DeQueue()
|
|
if attack.combatInvokeEntry == nil {
|
|
logger.LOG.Error("error attack data, attack value: %v", attack)
|
|
continue
|
|
}
|
|
|
|
hitInfo := new(proto.EvtBeingHitInfo)
|
|
err := pb.Unmarshal(attack.combatInvokeEntry.CombatData, hitInfo)
|
|
if err != nil {
|
|
logger.LOG.Error("parse combat invocations entity hit info error: %v", err)
|
|
continue
|
|
}
|
|
|
|
attackResult := hitInfo.AttackResult
|
|
logger.LOG.Debug("run attack handler, attackResult: %v", attackResult)
|
|
target := s.entityMap[attackResult.DefenseId]
|
|
if target == nil {
|
|
logger.LOG.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.fightProp != nil {
|
|
currHp = target.fightProp[uint32(constant.FightPropertyConst.FIGHT_PROP_CUR_HP)]
|
|
currHp -= damage
|
|
if currHp < 0 {
|
|
currHp = 0
|
|
}
|
|
target.fightProp[uint32(constant.FightPropertyConst.FIGHT_PROP_CUR_HP)] = currHp
|
|
}
|
|
|
|
// PacketEntityFightPropUpdateNotify
|
|
entityFightPropUpdateNotify := new(proto.EntityFightPropUpdateNotify)
|
|
entityFightPropUpdateNotify.EntityId = target.id
|
|
entityFightPropUpdateNotify.FightPropMap = make(map[uint32]float32)
|
|
entityFightPropUpdateNotify.FightPropMap[uint32(constant.FightPropertyConst.FIGHT_PROP_CUR_HP)] = currHp
|
|
for _, player := range s.playerMap {
|
|
gameManager.SendMsg(cmd.EntityFightPropUpdateNotify, player.PlayerID, player.ClientSeq, entityFightPropUpdateNotify)
|
|
}
|
|
|
|
combatData, err := pb.Marshal(hitInfo)
|
|
if err != nil {
|
|
logger.LOG.Error("create combat invocations entity hit info error: %v", err)
|
|
}
|
|
attack.combatInvokeEntry.CombatData = combatData
|
|
switch attack.combatInvokeEntry.ForwardType {
|
|
case proto.ForwardType_FORWARD_TYPE_TO_ALL:
|
|
combatInvokeEntryListAll = append(combatInvokeEntryListAll, attack.combatInvokeEntry)
|
|
case proto.ForwardType_FORWARD_TYPE_TO_ALL_EXCEPT_CUR:
|
|
fallthrough
|
|
case proto.ForwardType_FORWARD_TYPE_TO_ALL_EXIST_EXCEPT_CUR:
|
|
if combatInvokeEntryListOther[attack.uid] == nil {
|
|
combatInvokeEntryListOther[attack.uid] = make([]*proto.CombatInvokeEntry, 0)
|
|
}
|
|
combatInvokeEntryListOther[attack.uid] = append(combatInvokeEntryListOther[attack.uid], attack.combatInvokeEntry)
|
|
case proto.ForwardType_FORWARD_TYPE_TO_HOST:
|
|
combatInvokeEntryListHost = append(combatInvokeEntryListHost, attack.combatInvokeEntry)
|
|
default:
|
|
}
|
|
}
|
|
|
|
// PacketCombatInvocationsNotify
|
|
if len(combatInvokeEntryListAll) > 0 {
|
|
combatInvocationsNotifyAll := new(proto.CombatInvocationsNotify)
|
|
combatInvocationsNotifyAll.InvokeList = combatInvokeEntryListAll
|
|
for _, player := range s.playerMap {
|
|
gameManager.SendMsg(cmd.CombatInvocationsNotify, player.PlayerID, player.ClientSeq, combatInvocationsNotifyAll)
|
|
}
|
|
}
|
|
if len(combatInvokeEntryListOther) > 0 {
|
|
for uid, list := range combatInvokeEntryListOther {
|
|
combatInvocationsNotifyOther := new(proto.CombatInvocationsNotify)
|
|
combatInvocationsNotifyOther.InvokeList = list
|
|
for _, player := range s.playerMap {
|
|
if player.PlayerID == uid {
|
|
continue
|
|
}
|
|
gameManager.SendMsg(cmd.CombatInvocationsNotify, player.PlayerID, player.ClientSeq, combatInvocationsNotifyOther)
|
|
}
|
|
}
|
|
}
|
|
if len(combatInvokeEntryListHost) > 0 {
|
|
combatInvocationsNotifyHost := new(proto.CombatInvocationsNotify)
|
|
combatInvocationsNotifyHost.InvokeList = combatInvokeEntryListHost
|
|
gameManager.SendMsg(cmd.CombatInvocationsNotify, s.world.owner.PlayerID, s.world.owner.ClientSeq, combatInvocationsNotifyHost)
|
|
}
|
|
}
|