package mapPathfind import ( "f1-game/internal/algorithm/astar6" "f1-game/internal/code" "f1-game/internal/constant" "f1-game/internal/data" "f1-game/internal/enum" nameActor "f1-game/internal/name/actor" nameRemote "f1-game/internal/name/remote" "f1-game/internal/pb" "f1-game/internal/types" mapTypes "f1-game/nodes/map/internal/types" clog "github.com/cherry-game/cherry/logger" cactor "github.com/cherry-game/cherry/net/actor" ) type ( // actorPathfind 寻路子Actor,计划开启多个实例轮询调用 actorPathfind struct { cactor.Base barrierData mapTypes.BarrierData // 动态障碍数据 astar *astar6.AStar // a星寻路 } ) func newActorPathfind(barrierData mapTypes.BarrierData) *actorPathfind { var ( width = data.MapInfo().Width height = data.MapInfo().Height ) if width < 0 || height < 0 { clog.Errorf("map width or height is invalid. width = %v, height = %v", width, height) return nil } // 加载寻路数据 tileData := make([][]int32, width) for x := range width { tileData[x] = make([]int32, height) for y := range height { tileID, ok := data.PointToTileID(int32(x), int32(y)) if !ok { continue } tile := data.MapData().Tiles[tileID] if tile != nil && !tile.IsBlock() { tileData[x][y] = int32(tile.BlockType) } } } actor := &actorPathfind{ barrierData: barrierData.Clone(), } actor.astar = astar6.New(astar6.Config{ Min: types.Point{ X: 0, Y: 0, }, Max: types.Point{ X: width - 1, Y: height - 1, }, MapData: tileData, IsWalkableFunc: actor.isWalkable, }) return actor } func (p *actorPathfind) AliasID() string { return nameActor.Map_Pathfind } func (p *actorPathfind) OnInit() { p.Remote().Register(nameRemote.MapPathFind_Find, p.findPath) p.Remote().Register(nameRemote.MapPathFind_FindMulti, p.findMultiPath) p.Remote().Register(nameRemote.MapPathFind_SetBarriers, p.setBarriers) p.Remote().Register(nameRemote.MapPathFind_RemoveBarriers, p.removeBarriers) } // 计算寻路 func (p *actorPathfind) findPath(req *pb.FindPathRequest) (*pb.Points, int32) { ctx := astar6.NewFindContext(req.StartX, req.StartY, req.EndX, req.EndY, req.PlayerID, req.LeagueID, req.MarchType) nodes, ok := p.astar.FindPath(ctx) if !ok { return nil, code.MapPathFindFail } path := &pb.Points{} for _, node := range nodes { path.List = append(path.List, &pb.Point{ X: node.X(), Y: node.Y(), }) } return path, code.OK } // 多重寻路 func (p *actorPathfind) findMultiPath(req *pb.FindMultiPathRequest) (*pb.PointsList, int32) { findStrategy := enum.FindMultiPathType(req.FindType) switch findStrategy { case enum.FindMultiPath_AllSuccess: return p.findMultiPathAllSuccess(req.Requests) case enum.FindMultiPath_AnySuccess: return p.findMultiPathAnySuccess(req.Requests) case enum.FindMultiPath_SuccessOnly: return p.findMultiPathSuccessOnly(req.Requests) } return nil, code.MapPathFindFail } func (p *actorPathfind) findMultiPathAllSuccess(reqs []*pb.FindPathRequest) (*pb.PointsList, int32) { rsp := &pb.PointsList{} for _, request := range reqs { path, errCode := p.findPath(request) if code.IsFail(errCode) { return nil, errCode } rsp.List = append(rsp.List, path) } return rsp, code.OK } func (p *actorPathfind) findMultiPathAnySuccess(reqs []*pb.FindPathRequest) (*pb.PointsList, int32) { for _, request := range reqs { path, errCode := p.findPath(request) if code.IsOK(errCode) { rsp := &pb.PointsList{ List: []*pb.Points{path}, } return rsp, code.OK } } return nil, code.MapPathFindFail } func (p *actorPathfind) findMultiPathSuccessOnly(reqs []*pb.FindPathRequest) (*pb.PointsList, int32) { rsp := &pb.PointsList{} for _, request := range reqs { if path, errCode := p.findPath(request); code.IsOK(errCode) { rsp.List = append(rsp.List, path) } } return rsp, code.OK } // findLocalPath 在指定区域内寻路 func (p *actorPathfind) doFindPath(sx, sy, ex, ey int32, playerID, leagueID int64, marchType int32) (*pb.Points, int32) { ctx := astar6.NewFindContext(sx, sy, ex, ey, playerID, leagueID, marchType) nodes, ok := p.astar.FindPath(ctx) if !ok { return nil, code.MapPathFindFail } path := &pb.Points{} for _, node := range nodes { path.List = append(path.List, &pb.Point{ X: node.X(), Y: node.Y(), }) } return path, code.OK } func (p *actorPathfind) isWalkable(node *astar6.Node, ctx *astar6.FindContext) bool { // 当前坐标的障碍点 barrier, found := p.barrierData.Get(node.X(), node.Y()) // 没有障碍可直接通过 if !found { return true } // 通用 marchType 执行逻辑 marchType := enum.MarchType(ctx.MarchType()) switch marchType { case enum.MarchType_Unknow: // 默认情况,所有障碍点不可通行 return false case enum.MarchType_Retreat: // 撤退类型可以通过所有障碍点 return true default: // 如果是攻城或者掠夺是可以上敌方的城墙, TODO: 仅限终点,现在先不限制,等配置了攻城点再打开 if marchType == enum.MarchType_Siege || marchType == enum.MarchType_Snatch /*&& ctx.IsEndPoint(x, y)*/ { return true } // 其他行军指令,或者非攻城指令的终点 // 1. 只能通过友军的城墙 if barrier.PlayerID == ctx.PlayerID() || (barrier.LeagueID != 0 && barrier.LeagueID == ctx.LeagueID()) { return true } // 2. 中立的码头是可以通行的 // 码头 + 通道 if barrier.ConfigID == constant.MapCastle_Dock1 || barrier.ConfigID == constant.MapCastle_Dock2 { return barrier.LeagueID == 0 } } return false } func (p *actorPathfind) setBarriers(req *mapTypes.BarrierRequest) { p.barrierData.Add(req) } func (p *actorPathfind) removeBarriers(req types.Points) { for _, point := range req { p.barrierData.Remove(point.X, point.Y) } }