package mapLogic import ( capp "f1-game/internal/cherry_app" "f1-game/internal/code" "f1-game/internal/constant" "f1-game/internal/data" "f1-game/internal/enum" nameActor "f1-game/internal/name/actor" nameRoute "f1-game/internal/name/route" "f1-game/internal/pb" "f1-game/internal/sessions" "f1-game/internal/types" mapCall "f1-game/nodes/map/internal/call" "f1-game/nodes/map/internal/db" dbLogic "f1-game/nodes/map/internal/db/data/logic" mapTypes "f1-game/nodes/map/internal/types" ctime "github.com/cherry-game/cherry/extend/time" clog "github.com/cherry-game/cherry/logger" "github.com/cherry-game/cherry/net/parser/pomelo" ) type ( actor struct { pomelo.ActorBase *db.LogicTables now ctime.CherryTime nowMillis int64 updateID uint32 processTiles types.Map[int32, *db.LogicTileTable] // 需要处理的地块对象,key: tileID marchCheckFuncs map[enum.MarchType]marchCheckFunc // 行军调动前检查方法,key: MarchType marchTypeFuncs map[enum.MarchType]marchTypeFunc // 行军指令处理方法,key: MarchType marchStopFuncs map[enum.MarchState]marchStopFunc // 行军中止指令处理方法,key:MarchState marchRetreatFuncs map[enum.MarchState]marchRetreatFunc // 各种状态下行军撤退指令的处理方法,key:MarchState tileBattleResultFunc map[enum.TileBattleType]tileBattleResultFunc // 地块战斗结果处理方法,key:TileBattleType battleTimeoutFuncs map[enum.TileBattleType]battleTimeoutFunc // 地块战斗超时处理方法,key:TileBattleType occupyingCastles types.Map[int64, *db.LogicCastleTable] // 占领中的系统城 bornPos mapTypes.MapBornPos bornLimit types.Map[int32, int32] } marchCheckFunc func(marchTable *db.LogicMarchTable, targetObjectID int64, point *pb.Point) int32 marchTypeFunc func(*db.LogicMarchTable) marchStopFunc func(*db.LogicMarchTable) marchRetreatFunc func(*db.LogicMarchTable) tileBattleResultFunc func(*db.LogicTileTable, *dbLogic.TileBattle) battleTimeoutFunc func(tileTable *db.LogicTileTable, battleInfo *dbLogic.TileBattle) ) func NewActor(mapTables *db.LogicTables, bornPos mapTypes.MapBornPos, bornLimit types.Map[int32, int32]) *actor { actor := &actor{ LogicTables: mapTables, processTiles: types.NewMap[int32, *db.LogicTileTable](), occupyingCastles: types.NewMap[int64, *db.LogicCastleTable](), bornPos: bornPos, bornLimit: bornLimit, } actor.updateTime() actor.initMarchCheckFuncs() actor.initMarchTypeFuncs() actor.initMarchStopFuncs() actor.initMarchRetreatFuncs() actor.initTileBattleFunc() actor.initBattleTimeoutFuncs() actor.initMonster() actor.initTileProcess() actor.initOccupyingCastles() return actor } func (p *actor) AliasID() string { return nameActor.Map_Logic } func (p *actor) OnInit() { p.onInitRemote() p.onInitAOI() p.Timer().Add(constant.Duration_March, p.marchTimer) p.Timer().Add(constant.Duration_Tile, p.tileTimer) p.Timer().Add(constant.Duration_Res, p.resTimer) p.Timer().Add(constant.Duration_Build, p.buildTimer) p.Timer().Add(constant.Duration_Castle, p.castleTimer) p.Timer().Add(constant.Duration_Monster, p.monsterTimer) } // updateTime 全局时间更新 func (p *actor) updateTime() { p.now = ctime.Now() p.nowMillis = p.now.UnixMilli() } func (p *actor) newUpdateID() uint32 { // 单线程保证了原子性 p.updateID++ return p.updateID } // checkBuildable 检查是否可以建造 // TODO: 这个方法要重写,使 build 和 buildLeague 都可以调用 func (p *actor) checkBuildable(playerID, leagueID int64, x, y int32, buildRow *data.MapBuildRow) int32 { offsetPoints := buildRow.GetCoverOffsetPoints(x, y, true) for _, point := range offsetPoints { if errCode := p.checkPointAvailiable(point.X, point.Y, playerID, leagueID, buildRow); code.IsFail(errCode) { return errCode } } return code.OK // 其它通用检查,是否有阻挡等 } func (p *actor) checkPointAvailiable(x, y int32, playerID, leagueID int64, buildRow *data.MapBuildRow) int32 { if data.MapData().IsPointBlock(x, y) { clog.Debugf("point (%d, %d) not walkable.", x, y) return code.MapTile_NotWalkable } tileID, ok := data.PointToTileID(x, y) if !ok { return code.MapGetPointFail } tileTable := p.TileTables.GetValue(tileID) if tileTable.BuildObjectID > 0 || tileTable.CastleObjectID > 0 { clog.Debugf("point has block. Point = (%d, %d)", x, y) return code.MapTile_HasBlock } switch buildRow.BuildType { case enum.BuildType_Player: // 如果有资源,玩家可以建在自己的资源上 if resTable, ok := p.getResTableInTile(tileTable); ok { if resTable.PlayerID > 0 && resTable.PlayerID != playerID { return code.MapTile_HasBlock } } case enum.BuildType_League: // 如果有资源,不能建在玩家的资源上 if resTable, ok := p.getResTableInTile(tileTable); ok { if resTable.PlayerID > 0 { return code.MapTile_HasBlock } } // TODO: 有些同盟建筑是可以建在盟友的资源上的 default: return code.MapBuild_NotBuildable } return code.OK } func (p *actor) getResTableInTile(tileTable *db.LogicTileTable) (*db.LogicResTable, bool) { if tileTable.ResObjectID == 0 { return nil, false } resTable, ok := p.ResTables.Get(tileTable.ResObjectID) if !ok { clog.Warnf("[getResTableInTile] A bug: Tile(%d) get no Res(%d)", tileTable.TileID, tileTable.ResObjectID) return nil, false } return resTable, true } func (p *actor) getMonsterTableInTile(tileTable *db.LogicTileTable) (*db.LogicMonsterTable, bool) { if tileTable.MonsterObjectID == 0 { return nil, false } monsterTable, ok := p.MonsterTables.Get(tileTable.MonsterObjectID) if !ok { clog.Warnf("[getMonsterTableInTile] A bug: Tile(%d) get no Monster(%d)", tileTable.TileID, tileTable.MonsterObjectID) return nil, false } return monsterTable, true } // getMarchSpeed 获取行军速度 func (p *actor) getMarchSpeed(marchTable *db.LogicMarchTable, marchType enum.MarchType, marchState enum.MarchState) int64 { var speed int64 row, ok := data.MapMarch.GetByMarchType(marchType) if !ok { clog.Errorf("get march speed failed, marchType = %d", marchType) return constant.MapMoveSpeedMin } // 不能小于最小移动速度 speed = max(row.Speed*ctime.MillisecondsPerSecond, constant.MapMoveSpeedMin) // 测试用,加移速 if value, ok := data.GameDebug.GetDebugValue(constant.GameDebug_MapMarchSpeedUp); ok { speed >>= value } return speed } func (p *actor) convertPath(path []*pb.Point) types.Points { points := make(types.Points, 0, len(path)) for _, point := range path { points.Add(point.X, point.Y) } return points } // newMainCity 创建玩家主城 func (p *actor) newMainCity(stateID int32, playerID, leagueID int64) (*db.LogicBuildTable, int32) { // 随机出生点 tileID, ok := p.bornPos.Random(stateID) if !ok { clog.Debugf("[newMainCity] random born fail: stateID = %d, playerID = %d, leagueID = %d", stateID, playerID, leagueID) return nil, code.MapBornPoint_NotFound } buildRow, ok := data.MapBuild.GetByConfigID(constant.MapBuild_MainCity) if !ok { clog.Debugf("[newMainCity] config not found: stateID = %d, playerID = %d, leagueID = %d", stateID, playerID, leagueID) return nil, code.ConfigNotFound_MapBuild } x, y := data.TileIDToPoint(tileID) offsets := buildRow.GetCoverOffsetPoints(x, y, false) // 生成建筑 mainCity := db.NewLogicBuildTable(db.NewGUID(), capp.NodeID(), x, y, constant.MapBuild_MainCity, playerID, leagueID, offsets...) mainCity.LoadConfig(buildRow) p.buildAdd(mainCity) siegeTable := db.NewLogicSiegeTable(mainCity.ObjectID, mainCity.NodeID, mainCity.ConfigID) siegeTable.SetSiegeParams(buildRow.BuildingDurability, buildRow.Durability, buildRow.GarrisonArmies, buildRow.DefendArmy) siegeTable.SiegeReset(false) p.SiegeTables.Put(siegeTable.ObjectID, siegeTable) p.bornPos.Remove(tileID) clog.Infof("new main city success: stateID = %d, playerId = %d, leagueID = %d, point = %v", stateID, playerID, leagueID, mainCity.Point) return mainCity, code.OK } // changeMainCity 改变主城(随机出生点) func (p *actor) changeMainCity(stateID int32, mainCityObjectID int64) (*db.LogicBuildTable, int32) { mainCity, ok := p.BuildTables.Get(mainCityObjectID) if !ok { return nil, code.MapObjectNotFound } // 随机出生点 tileID, ok := p.bornPos.Random(stateID) if !ok { clog.Debugf("[changeMainCity] random born fail: stateID = %d, mainCityObjectID = %d", stateID, mainCityObjectID) return nil, code.MapBornPoint_NotFound } buildRow, ok := data.MapBuild.GetByConfigID(constant.MapBuild_MainCity) if !ok { clog.Debugf("[changeMainCity] config not found: stateID = %d, mainCityObjectID = %d", stateID, mainCityObjectID) return nil, code.ConfigNotFound_MapBuild } x, y := data.TileIDToPoint(tileID) offsets := buildRow.GetCoverOffsetPoints(x, y, false) // 先移除主城 p.buildRemove(mainCity) // 刷新主城位置 mainCity.Point.SetValue(x, y) mainCity.OffsetPoints = offsets mainCity.IsHide = false // 无论怎么都要显示 mainCity.IsDataChanged = true // 把主城血量刷新 siegeTable := p.SiegeTables.GetValue(mainCity.ObjectID) siegeTable.SiegeReset(false) // 再添加主城 p.buildAdd(mainCity) p.bornPos.Remove(tileID) clog.Infof("change main city success: stateID = %d, mainCityObjectID = %d, point = %v", stateID, mainCityObjectID, mainCity.Point) return mainCity, code.OK } // TODO: 把这个封装到 LogicTables GetObjectLeagueID 方法 func (p *actor) getMarchLeagueID(objectID int64, isLegion bool) int64 { if isLegion { if legionTable, ok := p.LegionTables.Get(objectID); ok { return legionTable.LeagueID } } if marchTable, ok := p.MarchTables.Get(objectID); ok { return marchTable.LeagueID } return 0 } func (p *actor) initOccupyingCastles() { for _, castleTable := range p.CastleTables { if castleTable.LeagueID != 0 && castleTable.CastleConfig.CastleType == pb.SysObjType_so_castle { p.occupyingCastles.Put(castleTable.ObjectID, castleTable) } } } // 更新对象全量信息 func (p *actor) UpdateObjectFull(objectID int64, objectType enum.ObjectType) { // 更新aoi mapCall.AOI.UpdateAll(objectID, objectType) // 更新对象所有者 switch objectType { case enum.ObjectType_Build: if buildTable, ok := p.BuildTables.Get(objectID); ok { p.PushOwnerMapObjectUpdate(buildTable.PlayerID, buildTable.ToOwnerMapObjectPb()) } case enum.ObjectType_Res: if resTable, ok := p.ResTables.Get(objectID); ok { p.PushOwnerMapObjectUpdate(resTable.PlayerID, resTable.ToOwnerMapObjectPb()) } } } func (p *actor) PushOwnerMapObjectAdd(playerID int64, object *pb.OwnerMapObject) { sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_OwnerMapObjectAdd, object) } func (p *actor) PushOwnerMapObjectUpdate(playerID int64, object *pb.OwnerMapObject) { sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_OwnerMapObjectUpdate, object) } func (p *actor) PushOwnerMapObjectDel(playerID int64, objectID int64) { sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_OwnerMapObjectDel, &pb.I64{Value: objectID}) } func (p *actor) BroadcastCastleState(castleTable *db.LogicCastleTable) { rsp := &pb.MapUICastles{} castlePb := &pb.MapUICastle{ ObjectID: castleTable.ObjectID, Point: castleTable.Point.ToProto(), ConfigID: castleTable.ConfigID, LeagueID: castleTable.LeagueID, } if castlePb.LeagueID != 0 { if leauge, found := pbMapLeagues.Get(castlePb.LeagueID); found { castlePb.LeagueName = leauge.LeagueName castlePb.LeagueAbbName = leauge.LeagueAbbName castlePb.FlagBg = leauge.FlagBg castlePb.FlagIcon = leauge.FlagIcon } } rsp.List = append(rsp.List, castlePb) sessions.PushMap(capp.NodeID(), nameRoute.PushMapPlayer_MapUICastlesPush, rsp) } func (p *actor) PushMapUIMarchUpsert(playerID int64, march *pb.MapUIMarch) { sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_MapUIMarchsPush, &pb.MapUIMarchs{List: []*pb.MapUIMarch{march}}) } func (p *actor) PushMapUIMarchState(playerID int64, state *pb.MapMarchState) { sessions.PushPlayer(playerID, nameRoute.PushMapPusher_MapUIMarchState, state) }