actor.go 13 KB

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