actor.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. package mapLogic
  2. import (
  3. capp "f1-game/internal/cherry_app"
  4. "f1-game/internal/code"
  5. "f1-game/internal/constant"
  6. "f1-game/internal/data"
  7. "f1-game/internal/enum"
  8. nameActor "f1-game/internal/name/actor"
  9. nameRoute "f1-game/internal/name/route"
  10. "f1-game/internal/pb"
  11. "f1-game/internal/sessions"
  12. "f1-game/internal/types"
  13. mapCall "f1-game/nodes/map/internal/call"
  14. "f1-game/nodes/map/internal/db"
  15. dbLogic "f1-game/nodes/map/internal/db/data/logic"
  16. mapTypes "f1-game/nodes/map/internal/types"
  17. ctime "github.com/cherry-game/cherry/extend/time"
  18. clog "github.com/cherry-game/cherry/logger"
  19. "github.com/cherry-game/cherry/net/parser/pomelo"
  20. )
  21. type (
  22. actor struct {
  23. pomelo.ActorBase
  24. *db.LogicTables
  25. now ctime.CherryTime
  26. nowMillis int64
  27. updateID uint32
  28. processTiles types.Map[int32, *db.LogicTileTable] // 需要处理的地块对象,key: tileID
  29. marchCheckFuncs map[enum.MarchType]marchCheckFunc // 行军调动前检查方法,key: MarchType
  30. marchTypeFuncs map[enum.MarchType]marchTypeFunc // 行军指令处理方法,key: MarchType
  31. marchStopFuncs map[enum.MarchState]marchStopFunc // 行军中止指令处理方法,key:MarchState
  32. marchRetreatFuncs map[enum.MarchState]marchRetreatFunc // 各种状态下行军撤退指令的处理方法,key:MarchState
  33. tileBattleResultFunc map[enum.TileBattleType]tileBattleResultFunc // 地块战斗结果处理方法,key:TileBattleType
  34. battleTimeoutFuncs map[enum.TileBattleType]battleTimeoutFunc // 地块战斗超时处理方法,key:TileBattleType
  35. occupyingCastles types.Map[int64, *db.LogicCastleTable] // 占领中的系统城
  36. bornPos mapTypes.MapBornPos
  37. bornLimit types.Map[int32, int32]
  38. }
  39. marchCheckFunc func(marchTable *db.LogicMarchTable, targetObjectID int64, point *pb.Point) int32
  40. marchTypeFunc func(*db.LogicMarchTable)
  41. marchStopFunc func(*db.LogicMarchTable)
  42. marchRetreatFunc func(*db.LogicMarchTable)
  43. tileBattleResultFunc func(*db.LogicTileTable, *dbLogic.TileBattle)
  44. battleTimeoutFunc func(tileTable *db.LogicTileTable, battleInfo *dbLogic.TileBattle)
  45. )
  46. func NewActor(mapTables *db.LogicTables, bornPos mapTypes.MapBornPos, bornLimit types.Map[int32, int32]) *actor {
  47. actor := &actor{
  48. LogicTables: mapTables,
  49. processTiles: types.NewMap[int32, *db.LogicTileTable](),
  50. occupyingCastles: types.NewMap[int64, *db.LogicCastleTable](),
  51. bornPos: bornPos,
  52. bornLimit: bornLimit,
  53. }
  54. actor.updateTime()
  55. actor.initMarchCheckFuncs()
  56. actor.initMarchTypeFuncs()
  57. actor.initMarchStopFuncs()
  58. actor.initMarchRetreatFuncs()
  59. actor.initTileBattleFunc()
  60. actor.initBattleTimeoutFuncs()
  61. actor.initMonster()
  62. actor.initTileProcess()
  63. actor.initOccupyingCastles()
  64. return actor
  65. }
  66. func (p *actor) AliasID() string {
  67. return nameActor.Map_Logic
  68. }
  69. func (p *actor) OnInit() {
  70. p.onInitRemote()
  71. p.onInitAOI()
  72. p.Timer().Add(constant.Duration_March, p.marchTimer)
  73. p.Timer().Add(constant.Duration_Tile, p.tileTimer)
  74. p.Timer().Add(constant.Duration_Res, p.resTimer)
  75. p.Timer().Add(constant.Duration_Build, p.buildTimer)
  76. p.Timer().Add(constant.Duration_Castle, p.castleTimer)
  77. p.Timer().Add(constant.Duration_Monster, p.monsterTimer)
  78. }
  79. // updateTime 全局时间更新
  80. func (p *actor) updateTime() {
  81. p.now = ctime.Now()
  82. p.nowMillis = p.now.UnixMilli()
  83. }
  84. func (p *actor) newUpdateID() uint32 {
  85. // 单线程保证了原子性
  86. p.updateID++
  87. return p.updateID
  88. }
  89. // checkBuildable 检查是否可以建造
  90. // TODO: 这个方法要重写,使 build 和 buildLeague 都可以调用
  91. func (p *actor) checkBuildable(playerID, leagueID int64, x, y int32, buildRow *data.MapBuildRow) int32 {
  92. offsetPoints := buildRow.GetCoverOffsetPoints(x, y, true)
  93. for _, point := range offsetPoints {
  94. if errCode := p.checkPointAvailiable(point.X, point.Y, playerID, leagueID, buildRow); code.IsFail(errCode) {
  95. return errCode
  96. }
  97. }
  98. return code.OK
  99. // 其它通用检查,是否有阻挡等
  100. }
  101. func (p *actor) checkPointAvailiable(x, y int32, playerID, leagueID int64, buildRow *data.MapBuildRow) int32 {
  102. if data.MapData().IsPointBlock(x, y) {
  103. clog.Debugf("point (%d, %d) not walkable.", x, y)
  104. return code.MapTile_NotWalkable
  105. }
  106. tileID, ok := data.PointToTileID(x, y)
  107. if !ok {
  108. return code.MapGetPointFail
  109. }
  110. tileTable := p.TileTables.GetValue(tileID)
  111. if tileTable.BuildObjectID > 0 || tileTable.CastleObjectID > 0 {
  112. clog.Debugf("point has block. Point = (%d, %d)", x, y)
  113. return code.MapTile_HasBlock
  114. }
  115. switch buildRow.BuildType {
  116. case enum.BuildType_Player:
  117. // 如果有资源,玩家可以建在自己的资源上
  118. if resTable, ok := p.getResTableInTile(tileTable); ok {
  119. if resTable.PlayerID > 0 && resTable.PlayerID != playerID {
  120. return code.MapTile_HasBlock
  121. }
  122. }
  123. case enum.BuildType_League:
  124. // 如果有资源,不能建在玩家的资源上
  125. if resTable, ok := p.getResTableInTile(tileTable); ok {
  126. if resTable.PlayerID > 0 {
  127. return code.MapTile_HasBlock
  128. }
  129. }
  130. // TODO: 有些同盟建筑是可以建在盟友的资源上的
  131. default:
  132. return code.MapBuild_NotBuildable
  133. }
  134. return code.OK
  135. }
  136. func (p *actor) getResTableInTile(tileTable *db.LogicTileTable) (*db.LogicResTable, bool) {
  137. if tileTable.ResObjectID == 0 {
  138. return nil, false
  139. }
  140. resTable, ok := p.ResTables.Get(tileTable.ResObjectID)
  141. if !ok {
  142. clog.Warnf("[getResTableInTile] A bug: Tile(%d) get no Res(%d)", tileTable.TileID, tileTable.ResObjectID)
  143. return nil, false
  144. }
  145. return resTable, true
  146. }
  147. func (p *actor) getMonsterTableInTile(tileTable *db.LogicTileTable) (*db.LogicMonsterTable, bool) {
  148. if tileTable.MonsterObjectID == 0 {
  149. return nil, false
  150. }
  151. monsterTable, ok := p.MonsterTables.Get(tileTable.MonsterObjectID)
  152. if !ok {
  153. clog.Warnf("[getMonsterTableInTile] A bug: Tile(%d) get no Monster(%d)", tileTable.TileID, tileTable.MonsterObjectID)
  154. return nil, false
  155. }
  156. return monsterTable, true
  157. }
  158. // getMarchSpeed 获取行军速度
  159. func (p *actor) getMarchSpeed(marchTable *db.LogicMarchTable, marchType enum.MarchType, marchState enum.MarchState) int64 {
  160. var speed int64
  161. row, ok := data.MapMarch.GetByMarchType(marchType)
  162. if !ok {
  163. clog.Errorf("get march speed failed, marchType = %d", marchType)
  164. return constant.MapMoveSpeedMin
  165. }
  166. // 不能小于最小移动速度
  167. speed = max(row.Speed*ctime.MillisecondsPerSecond, constant.MapMoveSpeedMin)
  168. // 测试用,加移速
  169. if value, ok := data.GameDebug.GetDebugValue(constant.GameDebug_MapMarchSpeedUp); ok {
  170. speed >>= value
  171. }
  172. return speed
  173. }
  174. func (p *actor) convertPath(path []*pb.Point) types.Points {
  175. points := make(types.Points, 0, len(path))
  176. for _, point := range path {
  177. points.Add(point.X, point.Y)
  178. }
  179. return points
  180. }
  181. // newMainCity 创建玩家主城
  182. func (p *actor) newMainCity(stateID int32, playerID, leagueID int64) (*db.LogicBuildTable, int32) {
  183. // 随机出生点
  184. tileID, ok := p.bornPos.Random(stateID)
  185. if !ok {
  186. clog.Debugf("[newMainCity] random born fail: stateID = %d, playerID = %d, leagueID = %d", stateID, playerID, leagueID)
  187. return nil, code.MapBornPoint_NotFound
  188. }
  189. buildRow, ok := data.MapBuild.GetByConfigID(constant.MapBuild_MainCity)
  190. if !ok {
  191. clog.Debugf("[newMainCity] config not found: stateID = %d, playerID = %d, leagueID = %d", stateID, playerID, leagueID)
  192. return nil, code.ConfigNotFound_MapBuild
  193. }
  194. x, y := data.TileIDToPoint(tileID)
  195. offsets := buildRow.GetCoverOffsetPoints(x, y, false)
  196. // 生成建筑
  197. mainCity := db.NewLogicBuildTable(db.NewGUID(), capp.NodeID(), x, y, constant.MapBuild_MainCity, playerID, leagueID, offsets...)
  198. mainCity.LoadConfig(buildRow)
  199. p.buildAdd(mainCity)
  200. siegeTable := db.NewLogicSiegeTable(mainCity.ObjectID, mainCity.NodeID, mainCity.ConfigID)
  201. siegeTable.SetSiegeParams(buildRow.BuildingDurability, buildRow.Durability, buildRow.GarrisonArmies, buildRow.DefendArmy)
  202. siegeTable.SiegeReset(false)
  203. p.SiegeTables.Put(siegeTable.ObjectID, siegeTable)
  204. p.bornPos.Remove(tileID)
  205. clog.Infof("new main city success: stateID = %d, playerId = %d, leagueID = %d, point = %v",
  206. stateID, playerID, leagueID, mainCity.Point)
  207. return mainCity, code.OK
  208. }
  209. // changeMainCity 改变主城(随机出生点)
  210. func (p *actor) changeMainCity(stateID int32, mainCityObjectID int64) (*db.LogicBuildTable, int32) {
  211. mainCity, ok := p.BuildTables.Get(mainCityObjectID)
  212. if !ok {
  213. return nil, code.MapObjectNotFound
  214. }
  215. // 随机出生点
  216. tileID, ok := p.bornPos.Random(stateID)
  217. if !ok {
  218. clog.Debugf("[changeMainCity] random born fail: stateID = %d, mainCityObjectID = %d", stateID, mainCityObjectID)
  219. return nil, code.MapBornPoint_NotFound
  220. }
  221. buildRow, ok := data.MapBuild.GetByConfigID(constant.MapBuild_MainCity)
  222. if !ok {
  223. clog.Debugf("[changeMainCity] config not found: stateID = %d, mainCityObjectID = %d", stateID, mainCityObjectID)
  224. return nil, code.ConfigNotFound_MapBuild
  225. }
  226. x, y := data.TileIDToPoint(tileID)
  227. offsets := buildRow.GetCoverOffsetPoints(x, y, false)
  228. // 先移除主城
  229. p.buildRemove(mainCity)
  230. // 刷新主城位置
  231. mainCity.Point.SetValue(x, y)
  232. mainCity.OffsetPoints = offsets
  233. mainCity.IsHide = false // 无论怎么都要显示
  234. mainCity.IsDataChanged = true
  235. // 把主城血量刷新
  236. siegeTable := p.SiegeTables.GetValue(mainCity.ObjectID)
  237. siegeTable.SiegeReset(false)
  238. // 再添加主城
  239. p.buildAdd(mainCity)
  240. p.bornPos.Remove(tileID)
  241. clog.Infof("change main city success: stateID = %d, mainCityObjectID = %d, point = %v", stateID, mainCityObjectID, mainCity.Point)
  242. return mainCity, code.OK
  243. }
  244. // TODO: 把这个封装到 LogicTables GetObjectLeagueID 方法
  245. func (p *actor) getMarchLeagueID(objectID int64, isLegion bool) int64 {
  246. if isLegion {
  247. if legionTable, ok := p.LegionTables.Get(objectID); ok {
  248. return legionTable.LeagueID
  249. }
  250. }
  251. if marchTable, ok := p.MarchTables.Get(objectID); ok {
  252. return marchTable.LeagueID
  253. }
  254. return 0
  255. }
  256. func (p *actor) initOccupyingCastles() {
  257. for _, castleTable := range p.CastleTables {
  258. if castleTable.LeagueID != 0 && castleTable.CastleConfig.CastleType == pb.SysObjType_so_castle {
  259. p.occupyingCastles.Put(castleTable.ObjectID, castleTable)
  260. }
  261. }
  262. }
  263. // 更新对象全量信息
  264. func (p *actor) UpdateObjectFull(objectID int64, objectType enum.ObjectType) {
  265. // 更新aoi
  266. mapCall.AOI.UpdateAll(objectID, objectType)
  267. // 更新对象所有者
  268. switch objectType {
  269. case enum.ObjectType_Build:
  270. if buildTable, ok := p.BuildTables.Get(objectID); ok {
  271. p.PushOwnerMapObjectUpdate(buildTable.PlayerID, buildTable.ToOwnerMapObjectPb())
  272. }
  273. case enum.ObjectType_Res:
  274. if resTable, ok := p.ResTables.Get(objectID); ok {
  275. p.PushOwnerMapObjectUpdate(resTable.PlayerID, resTable.ToOwnerMapObjectPb())
  276. }
  277. }
  278. }
  279. func (p *actor) PushOwnerMapObjectAdd(playerID int64, object *pb.OwnerMapObject) {
  280. sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_OwnerMapObjectAdd, object)
  281. }
  282. func (p *actor) PushOwnerMapObjectUpdate(playerID int64, object *pb.OwnerMapObject) {
  283. sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_OwnerMapObjectUpdate, object)
  284. }
  285. func (p *actor) PushOwnerMapObjectDel(playerID int64, objectID int64) {
  286. sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_OwnerMapObjectDel, &pb.I64{Value: objectID})
  287. }
  288. func (p *actor) BroadcastCastleState(castleTable *db.LogicCastleTable) {
  289. rsp := &pb.MapUICastles{}
  290. castlePb := &pb.MapUICastle{
  291. ObjectID: castleTable.ObjectID,
  292. Point: castleTable.Point.ToProto(),
  293. ConfigID: castleTable.ConfigID,
  294. LeagueID: castleTable.LeagueID,
  295. }
  296. if castlePb.LeagueID != 0 {
  297. if leauge, found := pbMapLeagues.Get(castlePb.LeagueID); found {
  298. castlePb.LeagueName = leauge.LeagueName
  299. castlePb.LeagueAbbName = leauge.LeagueAbbName
  300. castlePb.FlagBg = leauge.FlagBg
  301. castlePb.FlagIcon = leauge.FlagIcon
  302. }
  303. }
  304. rsp.List = append(rsp.List, castlePb)
  305. sessions.PushMap(capp.NodeID(), nameRoute.PushMapPlayer_MapUICastlesPush, rsp)
  306. }
  307. func (p *actor) PushMapUIMarchUpsert(playerID int64, march *pb.MapUIMarch) {
  308. sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_MapUIMarchsPush, &pb.MapUIMarchs{List: []*pb.MapUIMarch{march}})
  309. }
  310. func (p *actor) PushMapUIMarchState(playerID int64, state *pb.MapMarchState) {
  311. sessions.PushPlayer(playerID, nameRoute.PushMapPusher_MapUIMarchState, state)
  312. }