| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429 |
- package team
- import (
- capp "f1-game/internal/cherry_app"
- "f1-game/internal/code"
- "f1-game/internal/component/redis"
- "f1-game/internal/constant"
- "f1-game/internal/data"
- "f1-game/internal/enum"
- "f1-game/internal/event"
- "f1-game/internal/extend/math"
- facade "f1-game/internal/facade"
- nameRoute "f1-game/internal/name/route"
- "f1-game/internal/pb"
- "f1-game/internal/sessions"
- "f1-game/internal/types"
- ao "f1-game/nodes/game/player/asset/origin"
- mapCall "f1-game/nodes/map/internal/call"
- "f1-game/nodes/map/internal/db"
- dbMapPlayer "f1-game/nodes/map/internal/db/data/player"
- mapFacade "f1-game/nodes/map/internal/facade"
- "f1-game/nodes/map/player/hero"
- ctime "github.com/cherry-game/cherry/extend/time"
- clog "github.com/cherry-game/cherry/logger"
- cproto "github.com/cherry-game/cherry/net/proto"
- )
- var srv = &service{}
- func Service() *service {
- return srv
- }
- type service struct {
- facade.PlayerServiceBase[*db.MapPlayerTable]
- }
- func (p *service) OnLogin(playerTable *db.MapPlayerTable, session *cproto.Session) {
- if session.Contains(sessions.IsNewPlayer) {
- clog.Infof("map team init: playerID = %d", playerTable.PlayerID)
- // 初始化队伍
- p.initTeams(playerTable)
- }
- // 计算城外征兵 TODO 优化成定时结算
- var (
- nowMillisecond = ctime.Now().ToMillisecond()
- diffSecond = (nowMillisecond - playerTable.Stats.LastLogoutTime) / ctime.MillisecondsPerSecond
- )
- if success := p.doTeamOutsideAutoTroops(playerTable, int32(diffSecond)); success {
- playerTable.SetChangeData()
- }
- if playerTable.IsChangeData() {
- playerTable.Save2Queue()
- }
- }
- func (p *service) initTeams(playerTable *db.MapPlayerTable) {
- // 检测未添加的队伍
- teamCount := playerTable.Facilities.Attrs.GetI32(enum.AttrTeamCount)
- for i := int32(1); i <= teamCount; i++ {
- _, success := playerTable.Teams.AddTeam(i)
- if success {
- playerTable.SetChangeData()
- }
- }
- if playerTable.IsChangeData() {
- playerTable.Save2Queue()
- }
- }
- // func (p *service) OnLogined(playerTable *db.MapPlayerTable, session *cproto.Session) {
- // }
- // func (p *service) OnLogout(playerTable *db.MapPlayerTable) {
- // }
- // func (p *service) OnReset(playerTable *db.MapPlayerTable, time *ctime.CherryTime, byLogin bool) {
- // }
- // Push 推送队伍信息
- func (p *service) Push(playerTable *db.MapPlayerTable, session *cproto.Session) {
- resp := playerTable.Teams.ToProto()
- sessions.PushWithSession(session, nameRoute.PushMapPlayer_Team, &resp)
- // 城外征兵推送
- p.TeamOutsideTroopsPush(playerTable.PlayerID)
- }
- // ChangePush 推送变化的队伍信息
- func (p *service) ChangePush(playerID int64, teams ...*dbMapPlayer.Team) {
- session, found := sessions.GetSession(playerID)
- if !found {
- return
- }
- var (
- playerTable = db.GetMapPlayerTable(playerID)
- list []*pb.Team
- )
- // 检查玩家表是否存在
- if playerTable == nil {
- return
- }
- for _, team := range teams {
- proto := team.ToProto()
- list = append(list, &proto)
- }
- if len(list) == 0 {
- return
- }
- resp := &pb.Teams{
- List: list,
- }
- sessions.PushWithSession(session, nameRoute.PushMapPlayer_TeamChange, resp)
- }
- // 英雄状态更新推送
- func (p *service) HeroStateChangePush(playerID int64, updateHeroes ...*dbMapPlayer.Hero) {
- if len(updateHeroes) <= 0 {
- return
- }
- rsp := &pb.HeroStateResponse{}
- for _, hero := range updateHeroes {
- rsp.HeroState = append(rsp.HeroState, hero.ToHeroStateProto())
- }
- sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_HeroState, rsp)
- }
- func (p *service) CheckEnergy(playerID int64, teamID int32, energy int32) int32 {
- playerTable := db.GetMapPlayerTable(playerID)
- if playerTable == nil {
- return code.PlayerNotFound
- }
- team, ok := playerTable.Teams.GetTeam(teamID)
- if !ok {
- return code.TeamNotUnlock
- }
- for _, member := range team.Members {
- hero, ok := playerTable.Heroes.Get(member.HeroGUID)
- if !ok {
- return code.HeroNotExist
- }
- if hero.Energy < energy {
- return code.HeroEnergyLack
- }
- }
- return code.OK
- }
- // SubEnergy 队伍扣除体力
- func (p *service) SubEnergy(playerID int64, teamID int32, energy int32) int32 {
- var (
- playerTable = db.GetMapPlayerTable(playerID)
- updateHeroes []*dbMapPlayer.Hero
- )
- // 检查玩家表是否存在
- if playerTable == nil {
- return code.PlayerNotFound
- }
- team, ok := playerTable.Teams.GetTeam(teamID)
- if !ok {
- return code.TeamNotUnlock
- }
- for _, member := range team.Members {
- dbHero, ok := playerTable.Heroes.Get(member.HeroGUID)
- if !ok {
- return code.HeroNotExist
- }
- if !hero.Service().SubEnergy(dbHero, energy) {
- return code.HeroEnergyLack
- }
- updateHeroes = append(updateHeroes, dbHero)
- }
- playerTable.Save2Queue()
- // 推送英雄数据
- hero.Service().ChangePush(playerID, updateHeroes...)
- return code.OK
- }
- // 回城自动补兵 return 是否补兵成功
- func (p *service) CheckAutoAddTeamTroops(playerID int64, teamID int32) bool {
- if teamID <= 0 {
- return false
- }
- playerTable := db.GetMapPlayerTable(playerID)
- if playerTable == nil {
- clog.Warnf("[CheckAutoAddTeamTroops] player not found, playerID: %d", playerID)
- return false
- }
- team, ok := playerTable.Teams.GetTeam(teamID)
- if !ok {
- clog.Warnf("[CheckAutoAddTeamTroops] team not found, teamID: %d", teamID)
- return false
- }
- // 没设置自动补兵 跳过
- if !team.AutoAddTroops {
- return false
- }
- var (
- // 最终分兵数量
- finalMemberTroops = map[int32]*pb.I32I32{}
- // 英雄带兵上限 key 英雄上阵阵容位置
- heroArmsLimitMap = map[int32]int32{}
- // 通过士兵等级分组
- troopsIDGroupMemberMap = team.GetTroopsIDGroupMemberMap()
- costAssets = types.Assets{}
- )
- for troopsID, members := range troopsIDGroupMemberMap {
- // 可分配的兵力
- hasTroopsNum := int32(mapFacade.Storage.Get(playerID, troopsID))
- if hasTroopsNum <= 0 {
- continue
- }
- needCostCount := int32(0)
- for _, member := range members {
- // 当前需要补兵数量 = 带兵上限 - 当前剩余兵力 - 伤兵数
- needCostCount += member.TroopsLimit - member.TroopsNum - member.InjureTroops
- heroArmsLimitMap[member.TeamIndex] = member.TroopsLimit
- }
- // 不需要补兵.0
- if needCostCount <= 0 {
- continue
- }
- if needCostCount <= hasTroopsNum {
- // 全部补满
- for _, member := range members {
- data := pb.I32I32{
- Key: troopsID,
- Value: heroArmsLimitMap[member.TeamIndex] - member.TroopsNum - member.InjureTroops,
- }
- finalMemberTroops[member.TeamIndex] = &data
- }
- } else {
- // 优先持平补兵
- needCostCount = p.balanceTroopsDistribution(members, troopsID, heroArmsLimitMap, hasTroopsNum, finalMemberTroops)
- if needCostCount <= 0 {
- continue
- }
- }
- costAssets.Add(troopsID, int64(needCostCount))
- }
- if len(costAssets) <= 0 {
- return false
- }
- // 扣除兵力
- _, errCode := mapFacade.Storage.AssetSubs(playerID, costAssets, ao.TeamAutoTroopsCost)
- if code.IsFail(errCode) {
- clog.Warnf("[AutoAddTeamTroops] sub troop fail, playerID: %d, teamID: %d, errCode: %d", playerID, teamID, errCode)
- return false
- }
- // 更新队伍上阵英雄带兵数量
- team.UpdateTeamMemberTroopsNum(finalMemberTroops)
- // 同步到 redis 队伍信息
- p.UpdatePlayerTeams(playerID, team)
- // 发送分兵事件
- capp.PostEvent(event.NewTeamTroops(playerID, teamID))
- return true
- }
- // 持平补兵分配算法
- func (p *service) balanceTroopsDistribution(members []*dbMapPlayer.TeamMember, troopsID int32, heroArmsLimitMap map[int32]int32, hasTroopsNum int32, finalMemberTroops map[int32]*pb.I32I32) int32 {
- if len(members) == 0 || hasTroopsNum <= 0 {
- return 0
- }
- // 初始化当前兵力状态
- currentTroops := make(map[int32]int32)
- for _, member := range members {
- currentTroops[member.TeamIndex] = member.TroopsNum + member.InjureTroops
- finalMemberTroops[member.TeamIndex] = &pb.I32I32{Key: troopsID, Value: 0} // 初始化分配结果
- }
- var (
- intMaxValue = int32(1<<31 - 1)
- remaining = hasTroopsNum
- totalAdded = int32(0)
- )
- for remaining > 0 {
- // 找出所有未达到上限的英雄
- var availableHeroes []int32
- for _, member := range members {
- if currentTroops[member.TeamIndex] < heroArmsLimitMap[member.TeamIndex] {
- availableHeroes = append(availableHeroes, member.TeamIndex)
- }
- }
- if len(availableHeroes) == 0 {
- break // 所有英雄都已满
- }
- // 找出当前兵力最少的英雄们
- minTroops := intMaxValue
- var minHeroes []int32
- for _, teamIndex := range availableHeroes {
- if currentTroops[teamIndex] < minTroops {
- minTroops = currentTroops[teamIndex]
- minHeroes = []int32{teamIndex}
- } else if currentTroops[teamIndex] == minTroops {
- minHeroes = append(minHeroes, teamIndex)
- }
- }
- // 找出下一个更高的兵力水平(在可用英雄中)
- nextLevel := intMaxValue
- for _, teamIndex := range availableHeroes {
- if currentTroops[teamIndex] > minTroops && currentTroops[teamIndex] < nextLevel {
- nextLevel = currentTroops[teamIndex]
- }
- }
- // 如果没有找到更高的水平,使用最小上限值
- if nextLevel == intMaxValue {
- for _, teamIndex := range minHeroes {
- nextLevel = math.Min(nextLevel, heroArmsLimitMap[teamIndex])
- }
- }
- // 计算需要分配到下一个水平的总兵力
- totalNeeded := int32(0)
- for _, teamIndex := range minHeroes {
- needed := math.Min(nextLevel, heroArmsLimitMap[teamIndex]) - currentTroops[teamIndex]
- if needed > 0 {
- totalNeeded += needed
- }
- }
- if totalNeeded <= remaining {
- // 有足够兵力让所有最低英雄达到下一个水平
- for _, teamIndex := range minHeroes {
- addAmount := math.Min(nextLevel, heroArmsLimitMap[teamIndex]) - currentTroops[teamIndex]
- currentTroops[teamIndex] += addAmount
- finalMemberTroops[teamIndex].Value += addAmount
- totalAdded += addAmount
- remaining -= addAmount
- }
- } else {
- // 兵力不足,需要平均分配
- numHeroes := int32(len(minHeroes))
- baseAdd := remaining / numHeroes
- remainder := remaining % numHeroes
- for i, teamIndex := range minHeroes {
- addAmount := baseAdd
- if int32(i) < remainder {
- addAmount++
- }
- // 确保不超过上限
- maxPossibleAdd := heroArmsLimitMap[teamIndex] - currentTroops[teamIndex]
- if addAmount > maxPossibleAdd {
- addAmount = maxPossibleAdd
- }
- if addAmount > remaining {
- addAmount = remaining
- }
- currentTroops[teamIndex] += addAmount
- finalMemberTroops[teamIndex].Value += addAmount
- totalAdded += addAmount
- remaining -= addAmount
- if remaining <= 0 {
- break
- }
- }
- break
- }
- }
- return totalAdded
- }
- // 同步队伍信息到 redis
- func (p *service) UpdatePlayerTeams(playerID int64, teamList ...*dbMapPlayer.Team) {
- if len(teamList) == 0 {
- return
- }
- var battleTeamList []*pb.BattleTeam
- for _, team := range teamList {
- proto := team.ToBattleTeamProto()
- battleTeamList = append(battleTeamList, &proto)
- }
- redis.Game.UpdatePlayerTeams(playerID, battleTeamList...)
- }
- // 通过英雄列表获取羁绊列表
- func (p *service) GetFetterListByHeroList(playerID int64, heroIDList types.Set[int32]) types.Set[int32] {
- if len(heroIDList) < 1 {
- return nil
- }
- activeFetterIDs := types.Set[int32]{}
- for heroID := range heroIDList {
- fetterIDs := data.HeroFetter.GetFetterIDs(heroID)
- // 当前英雄没有羁绊,则跳过
- if len(fetterIDs) < 1 {
- continue
- }
- for fetterID := range fetterIDs {
- if activeFetterIDs.Contains(fetterID) {
- continue
- }
- fetterRow, found := data.HeroFetter.GetByID(fetterID)
- if !found {
- clog.Warnf("[GetFetterListByHeroList] fetter not found, playerID:%d, fetterID: %d", playerID, fetterID)
- continue
- }
- curNum := int32(0)
- for _, needHeroID := range fetterRow.HeroIDs {
- if heroIDList.Contains(needHeroID) {
- curNum++
- // 如果当前所需英雄数量已经满足,则直接添加羁绊ID,并跳过
- if curNum >= fetterRow.ActiveHeroNum {
- activeFetterIDs.Add(fetterID)
- break
- }
- }
- }
- }
- }
- return activeFetterIDs
- }
- // 获取英雄带兵上限
- func (p *service) GetHeroTroopsNum(playerID int64, heroID, teamIndex int32) int32 {
- playerTable := db.GetMapPlayerTable(playerID)
- if playerTable == nil {
- clog.Warnf("[GetHeroTroopsNum] player not found, playerID: %d", playerID)
- return 0
- }
- return p.doGetHeroTroopsNum(playerTable, heroID, teamIndex)
- }
- // 获取英雄带兵上限
- func (p *service) doGetHeroTroopsNum(playerTable *db.MapPlayerTable, heroID, teamIndex int32) int32 {
- var heroTroopsLimit int32
- // 1.英雄初始统兵数量
- heroRow, found := data.Hero.GetByID(heroID)
- if found {
- heroTroopsLimit += heroRow.InitTroop
- } else {
- clog.Warnf("[GetHeroTroopsNum] hero config not found, heroID: %d", heroID)
- }
- // 2.判断是否为主将 主将额外增加带兵数
- if constant.IsTeamMainIndex(teamIndex) {
- heroTroopsLimit += data.Const.TeamMainAddTroops
- }
- // 3.添加城建增加的带兵数
- heroTroopsLimit += int32(playerTable.Facilities.Attrs.Get(enum.AttrTeamArms))
- return heroTroopsLimit
- }
- // 部队羁绊加成
- func (p *service) TeamFetterAttrs(team *dbMapPlayer.Team, heroes dbMapPlayer.Heroes) {
- fetterIDs := team.FetterIDs
- if fetterIDs.Size() < 1 {
- return
- }
- for fetterID := range fetterIDs {
- for _, member := range team.Members {
- hero, found := heroes.Get(member.HeroGUID)
- if !found {
- continue
- }
- attrs := types.NewAttrs()
- fetterRow, found := data.HeroFetter.GetByID(fetterID)
- if !found {
- clog.Warnf("[teamFetterAttrs] fetter not found, heroID:%d, fetterID: %d", hero.HeroID, fetterID)
- continue
- }
- // 当前羁绊不存在该英雄,则跳过
- if !fetterRow.HeroIDs.Contains(hero.HeroID) {
- continue
- }
- for attrId, attrValue := range fetterRow.GetAttrs() {
- attrs.Add(attrId, attrValue)
- }
- member.FetterAttrs = attrs
- }
- }
- }
- // 更新队伍状态
- func (p *service) UpdateTeamStateByMarchState(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team, marchState enum.MarchState) {
- teamState := enum.MarchStateToTeamState(marchState)
- p.UpdateTeamState(playerTable, team, teamState)
- }
- // 更新队伍状态
- func (p *service) UpdateTeamState(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team, teamState enum.TeamState) {
- // 相同状态不处理
- if team.State == teamState {
- return
- }
- team.State = teamState
- // 回城待命
- if teamState == enum.TeamState_Standby {
- // 检测自动补兵
- p.CheckAutoAddTeamTroops(playerTable.PlayerID, team.TeamID)
- }
- playerTable.Save2Queue()
- clog.Debugf("[UpdateTeamState] PlayerID = %d, TeamID = %d, TeamState = %s", playerTable.PlayerID, team.TeamID, enum.GetTeamStateName(teamState))
- // 推送状态给客户端
- p.ChangePush(playerTable.PlayerID, team)
- }
- // 获取队伍行军同步数据
- func (p *service) GetTeamMarchSyncProto(playerTable *db.MapPlayerTable, dbTeam *dbMapPlayer.Team) *pb.MarchSync {
- var (
- members []*pb.MarchMember
- injureEndTime int64
- )
- for _, member := range dbTeam.Members {
- // 士兵负重
- var armWeight int64
- if armTypeRow, ok := data.ArmType.GetByID(member.TroopsID); ok {
- armWeight = armTypeRow.Weight
- }
- heroEntity, _ := playerTable.Heroes.Get(member.HeroGUID)
- // 获取带兵上限
- member.TroopsLimit = p.doGetHeroTroopsNum(playerTable, heroEntity.HeroID, member.TeamIndex)
- members = append(members, &pb.MarchMember{
- TeamIndex: member.TeamIndex,
- LineupIndex: member.Index,
- HeroGUID: heroEntity.GUID,
- HeroConfigID: heroEntity.HeroID,
- Energy: heroEntity.Energy,
- ArmID: member.TroopTypeID,
- ArmWeight: armWeight,
- TroopsMax: member.TroopsLimit,
- TroopsNum: member.TroopsNum,
- })
- injureEndTime = max(injureEndTime, heroEntity.InjuriesEndTime)
- }
- req := &pb.MarchSync{
- PlayerID: playerTable.PlayerID,
- LeagueID: playerTable.LeagueID,
- TeamID: dbTeam.TeamID,
- ObjectID: dbTeam.MarchObjectID,
- Point: playerTable.Point.ToProto(),
- Members: members,
- GameNodeID: playerTable.GameNodeID,
- InjureEndTime: injureEndTime * ctime.MillisecondsPerSecond,
- }
- // 出征行军更新玩家队伍兵力上限
- // TODO 这个需要拿出去不
- p.UpdatePlayerTeams(playerTable.PlayerID, dbTeam)
- return req
- }
- // 检查行军消耗是否足够
- func (p *service) CheckMarchCost(playerID int64, teamID int32, marchType enum.MarchType, pathLen int) int32 {
- row, ok := data.MapMarch.GetByMarchType(marchType)
- if !ok {
- return code.ConfigNotFound_MapMarch
- }
- // 检查体力是否足够
- energyCost := row.GetEnergyCost(pathLen)
- if row.EnergyCost > 0 {
- errCode := p.CheckEnergy(playerID, teamID, energyCost)
- if code.IsFail(errCode) {
- return errCode
- }
- }
- // 检查扣除资源
- if !row.Cost.IsEmpty() {
- if !mapFacade.Storage.IsAllEnough(playerID, row.Cost) {
- return code.ItemNotEnough
- }
- }
- return code.OK
- }
- // 行军消耗资源
- func (p *service) MarchCost(playerID int64, teamID int32, marchType enum.MarchType, pathLen int) int32 {
- row, ok := data.MapMarch.GetByMarchType(marchType)
- if !ok {
- return code.ConfigNotFound_MapMarch
- }
- // 扣除资源
- if !row.Cost.IsEmpty() {
- _, errCode := mapFacade.Storage.AssetSubs(playerID, row.Cost, ao.None, true)
- if code.IsFail(errCode) {
- return errCode
- }
- clog.Debugf("[marchCost] cost: PlayerID: %d, teamID: %d, cost: %v",
- playerID, teamID, row.Cost)
- }
- energyCost := row.GetEnergyCost(pathLen)
- // 扣体力
- if row.EnergyCost > 0 {
- p.SubEnergy(playerID, teamID, energyCost)
- clog.Debugf("[marchCost] sub energy: PlayerID: %d, teamID: %d, energy: %d",
- playerID, teamID, energyCost)
- }
- return code.OK
- }
- // 执行城外征兵逻辑
- func (p *service) DoTeamOutsideTroops(playerID int64, team *dbMapPlayer.Team, troopNums []*pb.I32I32) int32 {
- if len(troopNums) <= 0 {
- return code.IllegalArgument
- }
- var (
- // 检测征兵数量+当前兵力是否超出上限 & 获取征兵消耗
- costs = types.Assets{}
- playerTable = db.GetMapPlayerTable(playerID)
- )
- for _, troopsNum := range troopNums {
- // 征兵数量错误
- if troopsNum.Value <= 0 {
- return code.TeamTroopsNumError
- }
- teamMember, found := team.GetTeamMember(troopsNum.Key)
- if !found {
- return code.TeamMemberNotExist
- }
- // 判断当前是否已在城外征兵
- if teamMember.IsOutsideTroopsing() {
- return code.TeamOutsideConscripting
- }
- // 兵力数量超出上限
- if troopsNum.Value+teamMember.TroopsNum > teamMember.TroopsLimit {
- return code.TeamArmExceed
- }
- armLevelRow, found := data.ArmLevel.GetByID(teamMember.TroopsID)
- if !found {
- return code.ConfigNotExist_Conscript
- }
- singleCosts := armLevelRow.CalOutConscriptAssets(int64(troopsNum.Value))
- costs.AddAssets(singleCosts)
- }
- // 消耗道具
- _, errCode := mapFacade.Storage.AssetSubs(playerID, costs, ao.ConscriptOutsideCost, true)
- if code.IsFail(errCode) {
- return errCode
- }
- curTime := ctime.Now().ToSecond()
- for _, troopsNum := range troopNums {
- teamMember, _ := team.GetTeamMember(troopsNum.Key)
- teamMember.OutsideConscript.TroopsCount = 0
- teamMember.OutsideConscript.TotalTroopsCount = troopsNum.Value
- // 计算耗时
- armLevelRow, _ := data.ArmLevel.GetByID(teamMember.TroopsID)
- second := troopsNum.Value / armLevelRow.ConscriptNumSecond
- if troopsNum.Value%armLevelRow.ConscriptNumSecond > 0 {
- second++
- }
- // 计算结束时间戳
- teamMember.OutsideConscript.EndTime = curTime + int64(second)
- }
- // 通知地图自动征兵OK
- mapCall.Logic.AutoOutsideTroopsNotify(playerTable.PlayerID, team.TeamID, team.GetOutsideConscriptMaxTime())
- return code.OK
- }
- // 定时检测城外征兵
- func (p *service) CheckCanOutsideAutoTroopsTimer(playerID int64, nowSecond int64) {
- // 没到检测时间
- if nowSecond%data.Const.MapTroopsCheckInterval != 0 {
- return
- }
- playerTable := db.GetMapPlayerTable(playerID)
- if playerTable == nil {
- clog.Warnf("[CheckCanOutsideAutoTroopsTimer] player not found, playerID: %d", playerID)
- return
- }
- save := false
- for _, team := range playerTable.Teams {
- if team.IsCanOutsideAutoTroops() {
- // 执行城外征兵
- if ok := p.OutsideAutoTroops(playerTable, team); ok {
- save = true
- }
- }
- }
- if save {
- playerTable.Save2Queue()
- }
- }
- // 城外自动征兵定时器 //TODO 暂定每秒执行
- func (p *service) TeamOutsideAutoTroopsTimer(playerID int64, nowSecond int64) {
- playerTable := db.GetMapPlayerTable(playerID)
- if playerTable == nil {
- clog.Warnf("[TeamOutsideAutoTroopsTimer] player not found, playerID: %d", playerID)
- return
- }
- if save := p.doTeamOutsideAutoTroops(playerTable, 1); save {
- playerTable.Save2Queue()
- }
- }
- func (p *service) doTeamOutsideAutoTroops(playerTable *db.MapPlayerTable, seconds int32) bool {
- if seconds <= 0 {
- return false
- }
- updateTeams := []*dbMapPlayer.Team{}
- for _, team := range playerTable.Teams {
- update := false
- for _, member := range team.Members {
- if member.TroopsID <= 0 {
- continue
- }
- remainCount := member.GetRemainTroopsCount()
- // 城外征兵已完成
- if remainCount <= 0 {
- continue
- }
- armLevelRow, found := data.ArmLevel.GetByID(member.TroopsID)
- if !found {
- clog.Warnf("[doTeamOutsideAutoTroops] armLevelRow not found, playerID: %d, troopsID: %d", playerTable.PlayerID, member.TroopsID)
- continue
- }
- getCount := seconds * armLevelRow.ConscriptNumSecond
- if getCount > remainCount {
- getCount = remainCount
- }
- if member.TroopsNum+getCount > member.TroopsLimit {
- getCount = member.TroopsLimit - member.TroopsNum
- }
- if getCount <= 0 {
- continue
- }
- member.OutsideConscript.TroopsCount += getCount
- member.TroopsNum += getCount
- update = true
- }
- if update {
- // 队伍城外征兵已完成 重置征兵数据
- if team.IsTeamOutsideTroopsFinish() {
- for _, member := range team.Members {
- member.ResetOutsideTroops()
- }
- }
- updateTeams = append(updateTeams, team)
- }
- }
- if len(updateTeams) > 0 {
- // 推送城外征兵数据 TODO 是否合并到队伍信息中?
- p.TeamOutsideTroopsChangePush(playerTable.PlayerID, updateTeams...)
- // 推送队伍兵力变更
- p.UpdatePlayerTeams(playerTable.PlayerID, updateTeams...)
- // 推送队伍兵力
- p.ChangePush(playerTable.PlayerID, updateTeams...)
- return true
- }
- return false
- }
- // 城外征兵推送
- func (p *service) TeamOutsideTroopsPush(playerID int64) {
- teamPush := &pb.TeamOutsideTroopsList{}
- playerTable := db.GetMapPlayerTable(playerID)
- for _, team := range playerTable.Teams {
- teamPush.List = append(teamPush.List, team.ToOutsideTroopsProto())
- }
- if len(teamPush.List) > 0 {
- sessions.PushPlayer(playerID, nameRoute.PushMapPlayer_TeamOutsideTroops, teamPush)
- }
- }
- // 城外征兵推送
- func (p *service) TeamOutsideTroopsChangePush(playerID int64, teams ...*dbMapPlayer.Team) {
- if len(teams) <= 0 {
- return
- }
- teamPush := &pb.TeamOutsideTroopsList{}
- for _, team := range teams {
- teamPush.List = append(teamPush.List, team.ToOutsideTroopsProto())
- }
- session, found := sessions.GetSession(playerID)
- if !found {
- return
- }
- if len(teamPush.List) > 0 {
- sessions.PushWithSession(session, nameRoute.PushMapPlayer_TeamOutsideTroops, teamPush)
- }
- }
- // 取消城外征兵
- func (p *service) TeamOutsideTroopsCancel(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team, teamIndexs []int32) int32 {
- var (
- returnCosts = types.Assets{}
- playerID = playerTable.PlayerID
- memberCancels = []*dbMapPlayer.TeamMember{}
- )
- for _, teamIndex := range teamIndexs {
- teamMember, found := team.GetTeamMember(teamIndex)
- if !found {
- continue
- }
- // 当前没有城外征兵
- if teamMember.OutsideConscript.TroopsCount <= 0 {
- continue
- }
- // 存在没征完的士兵 返还奖励
- remainCount := teamMember.GetRemainTroopsCount()
- if remainCount > 0 {
- armLevelRow, found := data.ArmLevel.GetByID(teamMember.TroopsID)
- if !found {
- continue
- }
- singleCosts := armLevelRow.CalOutConscriptAssets(int64(remainCount))
- returnCosts.AddAssets(singleCosts)
- }
- memberCancels = append(memberCancels, teamMember)
- }
- // 没有任何返还 说明也没有城外征兵/城外征兵已完成
- if len(returnCosts) > 0 {
- _, errCode := mapFacade.Storage.AssetAdds(playerID, returnCosts, ao.ConscriptOutsideReturn, true)
- if code.IsFail(errCode) {
- return errCode
- }
- }
- // 重置需要需要的征兵数据
- for _, member := range memberCancels {
- member.ResetOutsideTroops()
- }
- // 通知地图自动征兵OK
- mapCall.Logic.AutoOutsideTroopsNotify(playerTable.PlayerID, team.TeamID, team.GetOutsideConscriptMaxTime())
- return code.OK
- }
- func (p *service) OutsideAutoTroops(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team) bool {
- if !team.IsAutoTroops {
- return false
- }
- // 计算所需征兵数量&各成员所需征兵数量
- var (
- teamMembers = map[int32]*pb.TeamOutsideTroopsMember{}
- oneTroopCosts = map[int32]types.Assets{}
- curHasAssets = map[int32]int64{}
- )
- for _, member := range team.Members {
- // 过滤没有士兵等级ID的成员
- if member.TroopsID <= 0 {
- continue
- }
- armLevelRow, found := data.ArmLevel.GetByID(member.TroopsID)
- if !found {
- clog.Warnf("[outsideAutoTroops] armLevel not found. playerID:%d, teamID: %d, troopsID: %d", playerTable.PlayerID, team.TeamID, member.TroopsID)
- continue
- }
- needTroops := member.TroopsLimit - member.TroopsNum
- if needTroops <= 0 {
- continue
- }
- teamMembers[member.TeamIndex] = &pb.TeamOutsideTroopsMember{
- TroopsID: member.TroopsID,
- TroopsNum: member.TroopsNum,
- TroopsLimit: member.TroopsLimit,
- }
- // 获取征一个士兵的消耗
- _, found = oneTroopCosts[member.TroopsID]
- if !found {
- costs := armLevelRow.CalOutConscriptAssets(1)
- oneTroopCosts[member.TroopsID] = costs
- // 获取当前道具的数量
- for _, cost := range costs {
- _, found := curHasAssets[cost.ID]
- if !found {
- hasNum := mapFacade.Storage.Get(playerTable.PlayerID, cost.ID)
- // TODO 如果数量<=0 是不是当前征兵失败
- curHasAssets[cost.ID] = hasNum
- }
- }
- }
- }
- // 如果当前没有征兵需求
- if len(teamMembers) <= 0 {
- return false
- }
- // 如果征兵数量>0 则获取每个成员最大征兵上限
- finalMemberTroops := p.HandleAutoTroops(curHasAssets, oneTroopCosts, teamMembers)
- if len(finalMemberTroops) <= 0 {
- clog.Warnf("[outsideAutoTroops] handle auto troops fail. playerID:%d, teamID:%d", playerTable.PlayerID, team.TeamID)
- return false
- }
- // 调用城外征兵核心逻辑
- errCode := p.DoTeamOutsideTroops(playerTable.PlayerID, team, finalMemberTroops)
- if code.IsFail(errCode) {
- clog.Warnf("[outsideAutoTroops] fail. playerID:%d, teamID: %d, errCode: %d", playerTable.PlayerID, team.TeamID, errCode)
- return false
- }
- // 同步给客户端
- p.TeamOutsideTroopsChangePush(playerTable.PlayerID, team)
- return true
- }
- // 规则:优先拉平队伍每个成员兵力(即优先给最小兵力的成员征兵),如果成员当前兵力相同后,则同步分兵
- // HandleAutoTroops 优化后的征兵逻辑 // TODO 后面看继续优化当前逻辑
- // curHasAssets: 当前库存
- // troopCosts: 兵种消耗配置 [兵种ID]消耗材料
- // teamMembers: [位置]部队成员信息(作为源数据查询,不直接修改)
- func (p *service) HandleAutoTroops(curHasAssets map[int32]int64, troopCosts map[int32]types.Assets,
- teamMembers map[int32]*pb.TeamOutsideTroopsMember) []*pb.I32I32 {
- // 初始化结果
- var (
- finalMemberTroops = map[int32]int32{} // [位置]征兵数量
- maxInt32 = int32(1<<31 - 1)
- maxInt64 = int64(1<<63 - 1)
- )
- // 追踪每个位置的当前兵力
- curTroops := make(map[int32]int32)
- for position, hero := range teamMembers {
- curTroops[position] = hero.TroopsNum
- }
- // 持续循环征兵,直到所有需求满足或资源耗尽
- for {
- // 找出当前最少兵力的英雄(且需要征兵)
- minTroops := maxInt32
- for _, troops := range curTroops {
- if troops < minTroops {
- minTroops = troops
- }
- }
- if minTroops == maxInt32 {
- break
- }
- // 找出所有兵力等于最少且需要征兵的位置
- // (curTroops中只保留未达到上限的位置)
- var canRecruitPositions []int32
- for position, troops := range curTroops {
- if troops == minTroops {
- canRecruitPositions = append(canRecruitPositions, position)
- }
- }
- if len(canRecruitPositions) == 0 {
- break
- }
- // 计算本轮所有英雄同步征兵的总材料消耗(每个英雄征1个兵)
- materialPerRound := map[int32]int64{}
- for _, position := range canRecruitPositions {
- troopID := teamMembers[position].TroopsID
- costs := troopCosts[troopID]
- for _, asset := range costs {
- materialPerRound[asset.ID] += asset.Num
- }
- }
- // 计算本轮最多能同步征兵多少次
- maxRound := maxInt64
- for itemID, cost := range materialPerRound {
- if cost == 0 {
- continue
- }
- stock := curHasAssets[itemID]
- if stock < cost {
- maxRound = 0
- break
- }
- if rounds := stock / cost; rounds < maxRound {
- maxRound = rounds
- }
- }
- if maxRound <= 0 {
- break
- }
- // 找出本轮英雄中剩余需求最小的
- minNeed := maxInt32
- for _, position := range canRecruitPositions {
- need := teamMembers[position].TroopsLimit - curTroops[position]
- if need < minNeed {
- minNeed = need
- }
- }
- // 找出下一个更高兵力的等级(用于拉平计算)
- // (curTroops中只保留未达到上限的位置)
- nextLevelTroops := maxInt32
- for _, troops := range curTroops {
- if troops > minTroops && troops < nextLevelTroops {
- nextLevelTroops = troops
- }
- }
- // 确定本轮征兵数量:取minNeed和(nextLevelTroops-minTroops)的较小值
- roundRecruit := int32(maxRound)
- if maxRound > maxInt64 {
- roundRecruit = maxInt32
- }
- if roundRecruit > minNeed {
- roundRecruit = minNeed
- }
- // 优化:当没有下一级兵力(所有可征兵位置兵力相同),不限制拉平
- // 这样可以一次性征到资源耗尽或征满,而不是每次只征1个
- if nextLevelTroops != maxInt32 {
- maxToLevel := nextLevelTroops - minTroops
- if roundRecruit > maxToLevel {
- roundRecruit = maxToLevel
- }
- }
- // 本轮执行征兵
- recruited := false
- for _, position := range canRecruitPositions {
- need := teamMembers[position].TroopsLimit - curTroops[position]
- if need <= 0 {
- continue
- }
- troopID := teamMembers[position].TroopsID
- costs, ok := troopCosts[troopID]
- if !ok || len(costs) == 0 {
- continue
- }
- // 确定实际征兵量
- actualRecruit := roundRecruit
- if need < actualRecruit {
- actualRecruit = need
- }
- // 更新征兵数据
- finalMemberTroops[position] += actualRecruit
- for _, asset := range costs {
- costAmount := int64(actualRecruit) * asset.Num
- curHasAssets[asset.ID] -= costAmount
- }
- // 更新当前兵力
- curTroops[position] += actualRecruit
- // 如果达到上限,从curTroops中删除该位置
- if teamMembers[position].TroopsLimit-curTroops[position] <= 0 {
- delete(curTroops, position)
- }
- if actualRecruit > 0 {
- recruited = true
- }
- }
- // 本轮无征兵时退出循环
- if !recruited {
- break
- }
- }
- result := []*pb.I32I32{}
- for position, value := range finalMemberTroops {
- result = append(result, &pb.I32I32{
- Key: position,
- Value: value,
- })
- }
- return result
- }
- // 行军至(回城\调度点\营帐)恢复伤兵,战败重伤的在重伤的逻辑恢复
- func (p *service) RecoveryInjureTroops(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team) {
- var marchTroops []*pb.MarchTroops
- for _, member := range team.Members {
- if member.InjureTroops <= 0 {
- continue
- }
- member.TroopsNum += member.InjureTroops
- marchTroops = append(marchTroops, &pb.MarchTroops{
- TeamIndex: member.TeamIndex,
- TroopsNum: member.InjureTroops,
- })
- // 重置伤兵数
- member.InjureTroops = 0
- }
- // 没有需要恢复的伤兵
- if len(marchTroops) <= 0 {
- return
- }
- playerTable.Save2Queue()
- // 更新地图队伍兵力数据
- mapCall.Logic.MarchSyncTroops(team.TeamID, team.MarchObjectID, marchTroops)
- // 同步队伍变更信息
- p.ChangePush(playerTable.PlayerID, team)
- // 兵力同步到redis
- p.UpdatePlayerTeams(playerTable.PlayerID, team)
- }
- // 刷新队伍阵容位置
- func (p *service) RefreshTeamLineup(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team) {
- if team == nil {
- return
- }
- // 1.没有主将 或 主将为步兵/骑兵,主将上阵2(下标1)号位;副将1和副将2分别上阵4(下标3)号位和6(下标5)号位 当前为默认值 下标从0开始
- teamIndex1, teamIndex2, teamIndex3 := int32(1), int32(3), int32(5)
- teamMember, found := team.GetTeamMember(constant.TeamMemberTeamIndex_Start)
- if found {
- // 获取主将兵种
- hero, found := playerTable.Heroes.Get(teamMember.HeroGUID)
- if found {
- heroRow, found := data.Hero.GetByID(hero.HeroID)
- if found {
- //2.主将为弓兵,主将上阵5(下标4)号位;副将1和副将2分别上阵1(下标0)号位和3(下标2)号位 下标从0开始
- if heroRow.Job == int32(enum.Job_Archer) {
- teamIndex1, teamIndex2, teamIndex3 = 4, 0, 2
- }
- } else {
- clog.Warnf("[RefreshTeamLineup]hero config not found, playerID:%d, heroID: %d", playerTable.PlayerID, hero.HeroID)
- }
- } else {
- clog.Warnf("[RefreshTeamLineup] hero not found, playerID:%d, heroGUID: %d", playerTable.PlayerID, teamMember.HeroGUID)
- }
- teamMember.Index = teamIndex1
- }
- teamMember2, found := team.GetTeamMember(constant.TeamMemberIndex_2)
- if found {
- teamMember2.Index = teamIndex2
- }
- teamMember3, found := team.GetTeamMember(constant.TeamMemberIndex_3)
- if found {
- teamMember3.Index = teamIndex3
- }
- }
- // 检测同队伍是否存在相同英雄
- func (p *service) CheckTeamSameHero(playerTable *db.MapPlayerTable, heroID, srcTeamID, srcTeamIndex, excludeTeamID, excludeIndex int32) bool {
- for _, team := range playerTable.Teams {
- for _, member := range team.Members {
- // 排除原来的位置
- if srcTeamID == team.TeamID && srcTeamIndex == member.TeamIndex {
- continue
- }
- // 排除现在上阵的队伍位置
- if team.TeamID == excludeTeamID && member.TeamIndex == excludeIndex {
- continue
- }
- hero, ok := playerTable.Heroes.Get(member.HeroGUID)
- if !ok {
- // 没找到返回true 防止上阵相同英雄
- return true
- }
- if hero.HeroID == heroID {
- return true
- }
- }
- }
- return false
- }
- // 英雄下阵处理
- func (p *service) DoTeamLeave(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team, teamIndex int32) (*dbMapPlayer.Hero, int32) {
- teamMember, ok := team.GetTeamMember(teamIndex)
- // 下阵位置不存在英雄
- if !ok || teamMember.HeroGUID == 0 {
- return nil, code.OK
- }
- heroEntity, ok := playerTable.Heroes.Get(teamMember.HeroGUID)
- if !ok {
- return nil, code.HeroNotExist
- }
- team.DelTeamMember(teamIndex)
- heroEntity.TeamID = 0
- if teamMember.TroopsNum > 0 {
- num := int64(teamMember.TroopsNum)
- // 返还兵力 直接返还兵力
- mapFacade.Storage.AssetAddOne(playerTable.PlayerID, teamMember.TroopsID, num, ao.TeamLeaveTroopsReturn)
- push := &pb.AssetList{}
- push.List = append(push.List, &pb.Asset{
- Id: teamMember.TroopsID,
- Num: num,
- })
- // 推送返还兵力
- sessions.PushPlayer(playerTable.PlayerID, nameRoute.PushMapPlayer_TeamTroopReturn, push)
- }
- return heroEntity, code.OK
- }
- // 检测是否可以城外征兵
- func (p *service) CheckCanOusideConscript(playerID int64, configID int32) bool {
- mapBuildRow, found := data.MapBuild.GetByConfigID(configID)
- if found {
- return mapBuildRow.CanOutsideConscript
- }
- // 地图建筑表没找到 找地图-城池表
- mapCastleRow, found := data.MapCastle.GetByConfigID(configID)
- if !found {
- clog.Warnf("[CheckCanOusideConscript] map castle not found. playerID:%d, configID:%d", playerID, configID)
- return false
- }
- return mapCastleRow.CanOutsideConscript
- }
- // 是否兵力不足
- func (p *service) IsTeamTroopsLack(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team) bool {
- var (
- total int32
- current = team.GetTeamTroopCount()
- )
- for _, member := range team.Members {
- total += member.TroopsLimit
- }
- ratio := float32(current) / float32(total) * 10000.0
- return ratio <= data.Const.TeamTroopsLackRatio
- }
- // 刷新队伍的带兵上限
- func (p *service) RefreshTeamTroopsLimit(playerTable *db.MapPlayerTable, teams ...*dbMapPlayer.Team) []*dbMapPlayer.Team {
- if len(teams) <= 0 {
- return nil
- }
- updateTeams := []*dbMapPlayer.Team{}
- for _, curTeam := range teams {
- if len(curTeam.Members) <= 0 {
- continue
- }
- var update bool
- for _, member := range curTeam.Members {
- if member.HeroGUID <= 0 {
- continue
- }
- heroEntity, found := playerTable.Heroes.Get(member.HeroGUID)
- if !found {
- clog.Warnf("[RefreshTeamTroopsLimit] fail, hero not found. playerID:%d, heroGUID:%d", playerTable.PlayerID, member.HeroGUID)
- continue
- }
- troopNums := p.GetHeroTroopsNum(playerTable.PlayerID, heroEntity.HeroID, member.TeamIndex)
- if member.TroopsLimit != troopNums {
- member.TroopsLimit = troopNums
- update = true
- }
- }
- if update {
- updateTeams = append(updateTeams, curTeam)
- }
- }
- // 更新队伍列表
- return updateTeams
- }
- // 获取队伍兵力万分比
- func (p *service) GetTeamTroopRatio(playerTable *db.MapPlayerTable, team *dbMapPlayer.Team) int32 {
- var (
- total int32
- count = team.GetTeamTroopCount()
- )
- for _, member := range team.Members {
- heroEntity, found := playerTable.Heroes.Get(member.HeroGUID)
- if !found {
- clog.Warnf("[GetTeamTroopRatio] fail, hero not found. playerID:%d, heroGUID:%d", playerTable.PlayerID, member.HeroGUID)
- return 0
- }
- total += p.GetHeroTroopsNum(playerTable.PlayerID, heroEntity.HeroID, member.TeamIndex)
- }
- return math.Round[float32, int32](float32(count) / float32(total) * constant.FRatioBase)
- }
|