| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427 |
- 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 p.doTeamOutsideAutoTroops(playerTable, int32(diffSecond)) {
- 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++ {
- if _, success := playerTable.Teams.AddTeam(i); 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 p.OutsideAutoTroops(playerTable, team) {
- 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 {
- 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)
- }
|