fix(fight): 修复战斗中属性变化与暴击判定逻辑

- 调整能力提升计算时机,确保命中后再恢复原始属性
- 暴击判断前置,仅在命中时计算暴击翻倍及破防逻辑
- 优化回合结束效果清除逻辑,增加状态存活判断

refactor(controller): 重构擂台相关接口返回结构体类型

- 将 ARENA_SET_OWENR、ARENA_FIGHT_OWENR 等函数的返回值统一改为
  NullOutboundInfo,并移除冗余字段返回
- 广播逻辑调整,统一使用 Broadcast
This commit is contained in:
2025-11-20 05:57:29 +08:00
parent c0b09c92c4
commit 53d6db7e17
37 changed files with 555 additions and 151 deletions

View File

@@ -301,10 +301,10 @@ var ErrorCodes = enum.New[struct {
ErrPermanentBanCheatTool ErrorCode `enum:"100401"` ErrPermanentBanCheatTool ErrorCode `enum:"100401"`
// 由于使用外挂,你的号码被24小时封停 // 由于使用外挂,你的号码被24小时封停
Err24hBanCheatTool ErrorCode `enum:"100402"` Err24hBanCheatTool ErrorCode `enum:"100402"`
// 由于使用外挂,你的号码被7天封停 // // 由于使用外挂,你的号码被7天封停
Err7dBanCheatTool ErrorCode `enum:"100403"` // Err7dBanCheatTool ErrorCode `enum:"100403"`
// 由于使用外挂,你的号码被14天封停 // // 由于使用外挂,你的号码被14天封停
Err14dBanCheatTool ErrorCode `enum:"100404"` // Err14dBanCheatTool ErrorCode `enum:"100404"`
// 由于强制改名,你的号码被踢下线 // 由于强制改名,你的号码被踢下线
ErrKickedForNameChange ErrorCode `enum:"100501"` ErrKickedForNameChange ErrorCode `enum:"100501"`
// 分配器中经验值不够 // 分配器中经验值不够

View File

@@ -15,7 +15,7 @@ import (
// 前端到后端无数据内容 空包 // 前端到后端无数据内容 空包
// 后端到前端无数据内容 空包 // 后端到前端无数据内容 空包
// 都需要通过2419包广播更新擂台状态 // 都需要通过2419包广播更新擂台状态
func (h Controller) ARENA_SET_OWENR(data *fight.ARENA_SET_OWENR, c *player.Player) (result *space.ARENA, err errorcode.ErrorCode) { func (h Controller) ARENA_SET_OWENR(data *fight.ARENA_SET_OWENR, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if atomic.CompareAndSwapUint32(&c.GetSpace().Owner.Flag, 0, 1) { if atomic.CompareAndSwapUint32(&c.GetSpace().Owner.Flag, 0, 1) {
@@ -23,8 +23,9 @@ func (h Controller) ARENA_SET_OWENR(data *fight.ARENA_SET_OWENR, c *player.Playe
c.GetSpace().Owner.Nick = c.GetInfo().Nick c.GetSpace().Owner.Nick = c.GetInfo().Nick
c.GetSpace().ARENA_Player = c //添加用户 c.GetSpace().ARENA_Player = c //添加用户
c.GetSpace().Broadcast(c, 2419, &c.GetSpace().Owner) c.GetSpace().Broadcast(c, 2419, c.GetSpace().Owner)
return &c.GetSpace().Owner, 0 c.SendPackCmd(2419, c.GetSpace().Owner)
return
} }
return nil, errorcode.ErrorCodes.ErrChampionExists return nil, errorcode.ErrorCodes.ErrChampionExists
@@ -36,29 +37,29 @@ func (h Controller) ARENA_SET_OWENR(data *fight.ARENA_SET_OWENR, c *player.Playe
// 后端到前端无数据内容 空包 // 后端到前端无数据内容 空包
// 还是后端主动发送2503的包给双方前端后 等待前端加载完毕 主动发送2404包通知后端开始战斗 // 还是后端主动发送2503的包给双方前端后 等待前端加载完毕 主动发送2404包通知后端开始战斗
// 并不会通知对方是否接受挑战。只要有人挑战就直接进入对战 // 并不会通知对方是否接受挑战。只要有人挑战就直接进入对战
func (h Controller) ARENA_FIGHT_OWENR(data *fight.ARENA_FIGHT_OWENR, c *player.Player) (result *space.ARENA, err errorcode.ErrorCode) { func (h Controller) ARENA_FIGHT_OWENR(data *fight.ARENA_FIGHT_OWENR, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
s := c.GetSpace()
//原子操作,修改擂台状态 //原子操作,修改擂台状态
if atomic.CompareAndSwapUint32(&s.Owner.Flag, 1, 2) { if atomic.CompareAndSwapUint32(&c.GetSpace().Owner.Flag, 1, 2) {
//成功发起擂台挑战后才修改我放状态 //成功发起擂台挑战后才修改我放状态
c.Fightinfo.Mode = info.BattleMode.SINGLE_MODE c.Fightinfo.Mode = info.BattleMode.SINGLE_MODE
c.Fightinfo.Status = info.BattleStatus.FIGHT_WITH_PLAYER c.Fightinfo.Status = info.BattleStatus.FIGHT_WITH_PLAYER
_, err = fight.NewFight(c, s.ARENA_Player, func(foi *info.FightOverInfo) { //我方邀请擂主挑战,我方先手 _, err = fight.NewFight(c, c.GetSpace().ARENA_Player, func(foi *info.FightOverInfo) { //我方邀请擂主挑战,我方先手
}) ///开始对战,房主方以及被邀请方 }) ///开始对战,房主方以及被邀请方
if err <= 0 { //发起战斗成功 if err <= 0 { //发起战斗成功
atomic.StoreUint32(&s.Owner.ChallengerID, c.GetInfo().UserID) //传回的指针赋值给ID atomic.StoreUint32(&c.GetSpace().Owner.ChallengerID, c.GetInfo().UserID) //传回的指针赋值给ID
c.GetSpace().Broadcast(c, 2419, &c.GetSpace().Owner)
c.SendPackCmd(2419, &c.GetSpace().Owner)
} else { } else {
//发起失败,改回1 //发起失败,改回1
atomic.StoreUint32(&s.Owner.Flag, 1) atomic.StoreUint32(&c.GetSpace().Owner.Flag, 1)
} }
s.Broadcast(c, 2419, &s.Owner) return
return &s.Owner, 0
} }
return nil, errorcode.ErrorCodes.ErrChampionExists return nil, errorcode.ErrorCodes.ErrChampionExists
@@ -69,7 +70,8 @@ func (h Controller) ARENA_FIGHT_OWENR(data *fight.ARENA_FIGHT_OWENR, c *player.P
// 后端到前端 // 后端到前端
func (h Controller) ARENA_GET_INFO(data *fight.ARENA_GET_INFO, c *player.Player) (result *space.ARENA, err errorcode.ErrorCode) { func (h Controller) ARENA_GET_INFO(data *fight.ARENA_GET_INFO, c *player.Player) (result *space.ARENA, err errorcode.ErrorCode) {
return &space.GetSpace(c.Info.MapID).Owner, 0 result = &c.GetSpace().Owner
return
} }
// public static const ARENA_UPFIGHT:uint = 2420; // public static const ARENA_UPFIGHT:uint = 2420;
@@ -77,14 +79,16 @@ func (h Controller) ARENA_GET_INFO(data *fight.ARENA_GET_INFO, c *player.Player)
// 前端到后端无数据内容 // 前端到后端无数据内容
// 后端到前端无数据内容 // 后端到前端无数据内容
// 都需要通过2419包广播更新擂台状态 // 都需要通过2419包广播更新擂台状态
func (h Controller) ARENA_UPFIGHT(data *fight.ARENA_UPFIGHT, c *player.Player) (result *space.ARENA, err errorcode.ErrorCode) { func (h Controller) ARENA_UPFIGHT(data *fight.ARENA_UPFIGHT, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
//原子操作,修改擂台状态 //原子操作,修改擂台状态
s := c.GetSpace()
if atomic.CompareAndSwapUint32(&s.Owner.UserID, c.GetInfo().UserID, 0) {
s.ARENA_Player = nil
s.Broadcast(c, 2419, &s.Owner) if atomic.CompareAndSwapUint32(&c.GetSpace().Owner.UserID, c.GetInfo().UserID, 0) {
return &s.Owner, 0 c.GetSpace().ARENA_Player = nil
c.GetSpace().Owner.Reset()
c.GetSpace().Broadcast(c, 2419, &c.GetSpace().Owner)
c.SendPackCmd(2419, &c.GetSpace().Owner)
return nil, -1
} }
return nil, errorcode.ErrorCodes.ErrChampionCannotCancel return nil, errorcode.ErrorCodes.ErrChampionCannotCancel
@@ -96,14 +100,14 @@ func (h Controller) ARENA_UPFIGHT(data *fight.ARENA_UPFIGHT, c *player.Player) (
// 后端到前端无数据内容 // 后端到前端无数据内容
// public static const ARENA_OWENR_OUT:uint = 2423; // public static const ARENA_OWENR_OUT:uint = 2423;
// 此包不清楚具体怎么触发 但已知此包为后端主动发送。不清楚什么情况下回用到 // 此包不清楚具体怎么触发 但已知此包为后端主动发送。不清楚什么情况下回用到
func (h Controller) ARENA_OWENR_ACCE(data *fight.ARENA_OWENR_ACCE, c *player.Player) (result *space.ARENA, err errorcode.ErrorCode) { func (h Controller) ARENA_OWENR_ACCE(data *fight.ARENA_OWENR_ACCE, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
s := c.GetSpace() s := c.GetSpace()
if atomic.LoadUint32(&s.Owner.UserID) != c.GetInfo().UserID && c.GetInfo().UserID != atomic.LoadUint32(&s.Owner.ChallengerID) { //说明已经有人了 if atomic.LoadUint32(&s.Owner.UserID) != c.GetInfo().UserID && c.GetInfo().UserID != atomic.LoadUint32(&s.Owner.ChallengerID) { //说明已经有人了
return nil, errorcode.ErrorCodes.Err24hBanCheatTool return nil, -1
} }
if c.GetInfo().UserID == atomic.LoadUint32(&s.Owner.UserID) { if c.GetInfo().UserID == atomic.LoadUint32(&s.Owner.UserID) {
c.Info.MaxArenaWins += 1
s.Owner.HostWins += 1 //连胜+1 s.Owner.HostWins += 1 //连胜+1
} else { } else {
@@ -116,5 +120,7 @@ func (h Controller) ARENA_OWENR_ACCE(data *fight.ARENA_OWENR_ACCE, c *player.Pla
atomic.StoreUint32(&s.Owner.Flag, 1) atomic.StoreUint32(&s.Owner.Flag, 1)
s.Broadcast(c, 2419, &s.Owner) s.Broadcast(c, 2419, &s.Owner)
return &s.Owner, 0 c.SendPackCmd(2419, &c.GetSpace().Owner)
return nil, -1
} }

View File

@@ -14,9 +14,15 @@ import (
func (h Controller) PET_MELEE(data *fight.StartPetWarInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { func (h Controller) PET_MELEE(data *fight.StartPetWarInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
c.Fightinfo.Mode = info.BattleMode.PET_MELEE c.Fightinfo.Mode = info.BattleMode.PET_MELEE
c.Fightinfo.Status = info.BattleStatus.FIGHT_WITH_PLAYER
c.Fightinfo.PlayerID = 0 c.Fightinfo.PlayerID = 0
c.JoinFight(func(p common.PlayerI) bool { err = c.JoinFight(func(p common.PlayerI) bool {
fight.NewFight(p, c, func(foi *info.FightOverInfo) { fight.NewFight(p, c, func(foi *info.FightOverInfo) {
if foi.Reason == 0 && foi.WinnerId == c.GetInfo().UserID { //我放获胜
c.Info.MessWin += 1
}
}) ///开始对战,房主方以及被邀请方 }) ///开始对战,房主方以及被邀请方
@@ -28,16 +34,20 @@ func (h Controller) PET_MELEE(data *fight.StartPetWarInboundInfo, c *player.Play
func (h Controller) PET_King(data *fight.PetKingJoinInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { func (h Controller) PET_King(data *fight.PetKingJoinInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
c.Fightinfo.Status = info.BattleStatus.FIGHT_WITH_PLAYER c.Fightinfo.Status = info.BattleStatus.FIGHT_WITH_PLAYER
c.Fightinfo.PlayerID = 0
switch data.Type { switch data.Type {
case 5: case 5:
c.Fightinfo.Mode = info.BattleMode.SINGLE_MODE c.Fightinfo.Mode = info.BattleMode.SINGLE_MODE
case 6: case 6:
c.Fightinfo.Mode = info.BattleMode.MULTI_MODE c.Fightinfo.Mode = info.BattleMode.MULTI_MODE
} }
c.JoinFight(func(p common.PlayerI) bool { err = c.JoinFight(func(p common.PlayerI) bool {
fight.NewFight(p, c, func(foi *info.FightOverInfo) { fight.NewFight(p, c, func(foi *info.FightOverInfo) {
if foi.Reason == 0 && foi.WinnerId == c.GetInfo().UserID { //我放获胜
c.Info.MonKingWin += 1
}
}) ///开始对战,房主方以及被邀请方 }) ///开始对战,房主方以及被邀请方
return true return true

View File

@@ -17,8 +17,10 @@ func (h Controller) OnPlayerHandleFightInvite(data *fight.HandleFightInviteInbou
return nil, errorcode.ErrorCodes.ErrInBattle return nil, errorcode.ErrorCodes.ErrInBattle
} }
c.Fightinfo.PlayerID = data.UserID c.Fightinfo.PlayerID = c.Info.UserID
c.Fightinfo.Mode = data.Mode c.Fightinfo.Mode = data.Mode
c.Fightinfo.Status = 1
c.Fightinfo.Type = 0
err = c.AgreeBattle(data.UserID, data.Flag, func(p common.PlayerI) bool { err = c.AgreeBattle(data.UserID, data.Flag, func(p common.PlayerI) bool {
_, err = fight.NewFight(p, c, func(foi *info.FightOverInfo) { _, err = fight.NewFight(p, c, func(foi *info.FightOverInfo) {
@@ -34,12 +36,14 @@ func (h Controller) OnPlayerInviteOtherFight(data *fight.InviteToFightInboundInf
//进入邀请,以及确认对战模式 //进入邀请,以及确认对战模式
if !atomic.CompareAndSwapUint32(&c.Fightinfo.PlayerID, 0, data.UserID) { //邀请前提是自己没邀请别人 // if !atomic.CompareAndSwapUint32(&c.Fightinfo.PlayerID, 0, data.UserID) { //邀请前提是自己没邀请别人
return nil, errorcode.ErrorCodes.ErrCannotPerformAction // return nil, errorcode.ErrorCodes.ErrCannotPerformAction
} // }
c.Fightinfo.PlayerID = data.UserID
c.Fightinfo.Mode = data.Mode c.Fightinfo.Mode = data.Mode
c.Fightinfo.Status = 1
c.Fightinfo.Type = 0
c.InvitePlayerToBattle() c.InvitePlayerToBattle()
return nil, 0 return nil, 0
@@ -50,7 +54,7 @@ func (h Controller) OnPlayerCanceledOtherInviteFight(data *fight.InviteFightCanc
if atomic.LoadUint32(&c.Fightinfo.Status) != 0 { //如果没有战斗状态,则不做任何处理 if atomic.LoadUint32(&c.Fightinfo.Status) != 0 { //如果没有战斗状态,则不做任何处理
atomic.StoreUint32(&c.Fightinfo.Status, 0) //设置状态为0 atomic.StoreUint32(&c.Fightinfo.Status, 0) //设置状态为0
return nil, 0
} }
//否则报错 //否则报错
return nil, errorcode.ErrorCodes.ErrCannotCancelBattle return nil, errorcode.ErrorCodes.ErrCannotCancelBattle

View File

@@ -64,7 +64,7 @@ func (h *Controller) Login(data *user.MAIN_LOGIN_IN, c gnet.Conn) (result *user.
} }
t.Info.UserID = data.Head.UserID t.Info.UserID = data.Head.UserID
t.Logintime = uint32(time.Now().Unix()) //保存时间戳 t.Logintime = uint32(time.Now().Unix()) //保存时间戳
t.Changemap = true
cool.Loger.Info(context.Background(), "用户上次重置日期", t.Info.LastResetTime.String()) cool.Loger.Info(context.Background(), "用户上次重置日期", t.Info.LastResetTime.String())
if !IsToday(t.Info.LastResetTime) { //判断是否是今天 if !IsToday(t.Info.LastResetTime) { //判断是否是今天
t.Info.LastResetTime = gtime.Now().Time t.Info.LastResetTime = gtime.Now().Time

View File

@@ -2,6 +2,7 @@ package controller
import ( import (
"blazing/common/socket/errorcode" "blazing/common/socket/errorcode"
"sync/atomic"
"blazing/logic/service/maphot" "blazing/logic/service/maphot"
"blazing/logic/service/maps" "blazing/logic/service/maps"
@@ -34,24 +35,25 @@ func (h Controller) MapHot(data *maphot.InInfo, c *player.Player) (result *mapho
return return
} }
func (h *Controller) MapLeave(data *maps.LeaveMapInboundInfo, c *player.Player) (result *info.LeaveMapOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 func (h *Controller) MapLeave(data *maps.LeaveMapInboundInfo, c *player.Player) (result *info.LeaveMapOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的
//result = &maps.LeaveMapOutboundInfo{UserID: c.GetUserID()}
c.Canmon = false
c.Changemap = true //可以刷怪
//data.Broadcast(c.Info.MapID, info.LeaveMapOutboundInfo{UserID: c.Info.UserID}) //同步广播
atomic.StoreUint32(&c.Canmon, 0)
//data.Broadcast(c.Info.MapID, info.LeaveMapOutboundInfo{UserID: c.Info.UserID}) //同步广播
result = &info.LeaveMapOutboundInfo{
UserID: c.Info.UserID,
}
defer c.GetSpace().LeaveMap(c) //玩家离开地图 defer c.GetSpace().LeaveMap(c) //玩家离开地图
// 如果有正在运行的刷怪协程,发送停止信号 // 如果有正在运行的刷怪协程,发送停止信号
//c.Info.MapID = 0 // 重置当前地图 //c.Info.MapID = 0 // 重置当前地图
return &info.LeaveMapOutboundInfo{UserID: c.Info.UserID}, 0 return
} }
func (h *Controller) MapList(data *maps.ListMapPlayerInboundInfo, c *player.Player) (result *info.ListMapPlayerOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 func (h *Controller) MapList(data *maps.ListMapPlayerInboundInfo, c *player.Player) (result *info.ListMapPlayerOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的
result = &info.ListMapPlayerOutboundInfo{ result = &info.ListMapPlayerOutboundInfo{
Player: c.GetSpace().GetInfo(), Player: c.GetSpace().GetInfo(),
} }
atomic.StoreUint32(&c.Canmon, 2)
c.Canmon = true //可以刷怪
return return
} }

View File

@@ -13,12 +13,12 @@ func (h *Controller) NonoFollowOrHome(data *nono.NonoFollowOrHomeInInfo, c *play
c.Info.NONO.Flag = data.Flag c.Info.NONO.Flag = data.Flag
result = &nono.NonoFollowOutInfo{ result = &nono.NonoFollowOutInfo{
UserID: data.Head.UserID, UserID: data.Head.UserID,
SuperStage: data.Flag, SuperStage: data.Flag,
Flag: data.Flag, Flag: data.Flag,
Nick: "", Nick: "",
Color: 0, Color: 0,
Power: 0, Power: 0,
} }
return return
@@ -44,14 +44,17 @@ func (h *Controller) SwitchFlying(data *nono.SwitchFlyingInboundInfo, c *player.
result = &nono.SwitchFlyingOutboundInfo{ result = &nono.SwitchFlyingOutboundInfo{
UserId: data.Head.UserID, UserId: data.Head.UserID,
Type: data.Type, Type: data.Type,
} }
c.GetSpace().Broadcast(c, data.Head.CMD, result)
return return
} }
func (h *Controller) PlayerPetCure(data *nono.PetCureInboundInfo, c *player.Player) (result *nono.PetCureOutboundEmpty, err errorcode.ErrorCode) { //这个时候player应该是空的 func (h *Controller) PlayerPetCure(data *nono.PetCureInboundInfo, c *player.Player) (result *nono.PetCureOutboundEmpty, err errorcode.ErrorCode) { //这个时候player应该是空的
if c.GetSpace().Owner.UserID == c.Info.UserID {
return result, errorcode.ErrorCodes.ErrChampionCannotHeal
}
for i := 0; i < len(c.Info.PetList); i++ { for i := 0; i < len(c.Info.PetList); i++ {
c.Info.PetList[i].Cure() c.Info.PetList[i].Cure()

View File

@@ -63,9 +63,13 @@ func (h *Controller) PetRelease(
err errorcode.ErrorCode) { //这个时候player应该是空的 err errorcode.ErrorCode) { //这个时候player应该是空的
//放入背包=数据库置1+添加到背包+pet release发包 仓库=数据库置0+移除背包 设置首发等于取到首发精灵后重新排序 //放入背包=数据库置1+添加到背包+pet release发包 仓库=数据库置0+移除背包 设置首发等于取到首发精灵后重新排序
//这里只修改,因为添加和移除背包在宠物获取时已经做了 //这里只修改,因为添加和移除背包在宠物获取时已经做了
result = &pet.PetReleaseOutboundInfo{} result = &pet.PetReleaseOutboundInfo{}
result.Flag = uint32(data.Flag) result.Flag = uint32(data.Flag)
//擂台住不能换精灵
if c.GetSpace().Owner.UserID == c.Info.UserID {
return result, errorcode.ErrorCodes.ErrChampionCannotSwitch
}
switch data.Flag { switch data.Flag {
case 0: case 0:
var temp []model.PetInfo var temp []model.PetInfo
@@ -131,7 +135,9 @@ func (h *Controller) PlayerShowPet(
} }
func (h *Controller) PetOneCure( func (h *Controller) PetOneCure(
data *pet.PetOneCureInboundInfo, c *player.Player) (result *pet.PetOneCureOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的 data *pet.PetOneCureInboundInfo, c *player.Player) (result *pet.PetOneCureOutboundInfo, err errorcode.ErrorCode) { //这个时候player应该是空的
if c.GetSpace().Owner.UserID == c.Info.UserID {
return result, errorcode.ErrorCodes.ErrChampionCannotHeal
}
_, onpet, ok := utils.FindWithIndex(c.Info.PetList, func(item model.PetInfo) bool { _, onpet, ok := utils.FindWithIndex(c.Info.PetList, func(item model.PetInfo) bool {
return item.CatchTime == data.CatchTime return item.CatchTime == data.CatchTime
}) })

View File

@@ -98,6 +98,24 @@ func (h Controller) ChangeNONOColor(data *user.ChangeNONOColorInboundInfo, c *pl
Sataus: c.Info.UserID, Sataus: c.Info.UserID,
Color: c.Info.Color, Color: c.Info.Color,
} }
c.GetSpace().Broadcast(c, data.Head.CMD, result)
return result, 0 return
}
func (h Controller) DANCE_ACTION(data *user.C2SDanceAction, c *player.Player) (result *user.S2CDanceAction, err errorcode.ErrorCode) {
result = &user.S2CDanceAction{
Type: data.Type,
UserID: c.Info.UserID,
}
c.GetSpace().Broadcast(c, data.Head.CMD, result)
return
}
func (h Controller) PEOPLE_TRANSFROM(data *user.C2SPEOPLE_TRANSFROM, c *player.Player) (result *user.S2CPEOPLE_TRANSFROM, err errorcode.ErrorCode) {
result = &user.S2CPEOPLE_TRANSFROM{
SuitID: data.SuitID,
UserID: c.Info.UserID,
}
c.GetSpace().Broadcast(c, data.Head.CMD, result)
return
} }

View File

@@ -39,7 +39,7 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mhmtszr/concurrent-swiss-map v1.0.9 // indirect github.com/mhmtszr/concurrent-swiss-map v1.0.9
github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/redis/go-redis/v9 v9.5.1 // indirect github.com/redis/go-redis/v9 v9.5.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect

View File

@@ -15,6 +15,6 @@ type PlayerI interface {
InvitePlayer(PlayerI) InvitePlayer(PlayerI)
SetFightC(FightI) SetFightC(FightI)
QuitFight() QuitFight()
CanFight() bool
SendPackCmd(uint32, any) SendPackCmd(uint32, any)
} }

View File

@@ -221,8 +221,6 @@ func (f *FightC) startBattle(startInfo info.FightStartOutboundInfo) {
ff.Player.SendPackCmd(2504, &startInfo) ff.Player.SendPackCmd(2504, &startInfo)
}) })
// 标记战斗已启动(原注释逻辑)
// f.running = true
} }
var Fightpool *ants.Pool var Fightpool *ants.Pool

View File

@@ -9,8 +9,8 @@ import (
// 基类特性 // 基类特性
type NewSel0 struct { type NewSel0 struct {
node.EffectNode node.EffectNode
Catchtime int //保存精灵的唯一指令,保证在上场时候正确注入 Catchtime int //保存精灵的唯一指令,保证在上场时候正确注入
Hide bool //是否隐藏 ,所属精灵下场后特性就应该消失 //Hide bool //是否隐藏 ,所属精灵下场后特性就应该消失
//如果2只精灵一个特性怎么办每次切精灵注入特性 //如果2只精灵一个特性怎么办每次切精灵注入特性
} }

View File

@@ -0,0 +1,39 @@
package effect
import (
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node"
"github.com/shopspring/decimal"
)
type Effect130 struct {
node.EffectNode // 仅继承基础效果节点,不嵌入条件函数
}
// -----------------------------------------------------------
// 核心共性逻辑:命中且满足条件时,附加固定伤害
// -----------------------------------------------------------
func (e *Effect130) OnSkill() bool {
// 1. 命中判定失败,不触发
if !e.Hit() {
return true
}
if e.Ctx().Opp.CurrentPet.PetInfo.Gender == e.Args()[0] {
return true
}
// 4. 附加固定伤害从SideEffectArgs[0]获取伤害值)
damageValue := decimal.NewFromInt(int64(e.SideEffectArgs[1]))
e.Ctx().Opp.Damage(e.Ctx().Our, &info.DamageZone{
Type: info.DamageType.Fixed,
Damage: damageValue,
})
return true
}
func init() {
input.InitEffect(input.EffectType.Skill, 130, &Effect130{})
}

View File

@@ -0,0 +1,29 @@
package effect
import (
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node"
)
/**
* 消除对手回合类效果
*/
type Effect180 struct {
node.EffectNode
}
func init() {
ret := &Effect180{}
input.InitEffect(input.EffectType.Skill, 180, ret)
}
// 命中之后
func (e *Effect180) OnSkill() bool {
if !e.Hit() {
return true
}
e.Ctx().Opp.CancelTurn(e.Ctx().Our)
return true
}

View File

@@ -0,0 +1,36 @@
package effect
import (
"blazing/common/utils"
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node"
)
/**
* 无视对手双防能力提升状态
*/
type Effect195 struct {
node.EffectNode
}
func init() {
ret := &Effect195{}
input.InitEffect(input.EffectType.Skill, 195, ret)
}
// 命中之后
func (e *Effect195) OnSkill() bool {
if !e.Hit() {
return true
}
e.Ctx().Opp.CancelTurn(e.Ctx().Our)
return true
}
func (e *Effect195) Skill_Hit() bool {
e.Ctx().Opp.Prop[1] = utils.Min(e.Ctx().Opp.Prop[1], 0)
e.Ctx().Opp.Prop[3] = utils.Min(e.Ctx().Opp.Prop[3], 0)
return true
}

View File

@@ -43,7 +43,7 @@ func (e *Effect45) SetArgs(t *input.Input, a ...int) {
func (e *Effect45) Alive(t ...bool) bool { func (e *Effect45) Alive(t ...bool) bool {
if !e.GetBool(t...) { //说明到了回合结束取消节点,那么就将变化过的属性变化回来 if !e.GetBool(t...) && e.Hit() { //说明到了回合结束取消节点,那么就将变化过的属性变化回来
//还原属性 //还原属性
e.Ctx().Our.CurrentPet.Info.Prop[1] = e.oldtype e.Ctx().Our.CurrentPet.Info.Prop[1] = e.oldtype
} }

View File

@@ -0,0 +1,51 @@
package effect
import (
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/fight/node"
)
/**
* n回合内令对手使用的属性技能无效
*/
type Effect478 struct {
node.EffectNode
can bool
}
func init() {
ret := &Effect478{}
input.InitEffect(input.EffectType.Skill, 478, ret)
}
// 命中之后
func (e *Effect478) OnSkill() bool {
if !e.Hit() {
return true
}
e.can = true
return true
}
func (e *Effect478) Skill_Hit_ex() bool {
if !e.Hit() {
return true
}
if !e.can {
return true
}
if e.Ctx().SkillEntity.Category() != info.Category.STATUS {
return true
}
e.Ctx().Opp.EffectCache = make([]input.Effect, 0)
return true
}
func (e *Effect478) SetArgs(t *input.Input, a ...int) {
e.EffectNode.SetArgs(t, a...)
e.EffectNode.Duration(e.EffectNode.SideEffectArgs[0])
}

View File

@@ -34,7 +34,7 @@ func (e *Effect51) SetArgs(t *input.Input, a ...int) {
func (e *Effect51) Alive(t ...bool) bool { func (e *Effect51) Alive(t ...bool) bool {
if !e.GetBool(t...) { //说明到了回合结束取消节点,那么就将变化过的属性变化回来 if !e.GetBool(t...) && e.Hit() { //说明到了回合结束取消节点,那么就将变化过的属性变化回来
//还原属性 //还原属性
e.Ctx().Our.CurrentPet.Info.Prop[0] = e.oldtype e.Ctx().Our.CurrentPet.Info.Prop[0] = e.oldtype
} }

View File

@@ -32,7 +32,7 @@ func (e *Effect55) SetArgs(t *input.Input, a ...int) {
func (e *Effect55) Alive(t ...bool) bool { func (e *Effect55) Alive(t ...bool) bool {
if !e.GetBool(t...) { //说明到了回合结束取消节点,那么就将变化过的属性变化回来 if !e.GetBool(t...) && e.Hit() { //说明到了回合结束取消节点,那么就将变化过的属性变化回来
//还原属性 //还原属性
e.Ctx().Our.CurrentPet.PetInfo.Type, e.Ctx().Opp.CurrentPet.PetInfo.Type = e.Ctx().Opp.CurrentPet.PetInfo.Type, e.Ctx().Our.CurrentPet.PetInfo.Type e.Ctx().Our.CurrentPet.PetInfo.Type, e.Ctx().Opp.CurrentPet.PetInfo.Type = e.Ctx().Opp.CurrentPet.PetInfo.Type, e.Ctx().Our.CurrentPet.PetInfo.Type
} }

View File

@@ -34,7 +34,7 @@ func (e *Effect56) SetArgs(t *input.Input, a ...int) {
func (e *Effect56) Alive(t ...bool) bool { func (e *Effect56) Alive(t ...bool) bool {
if !e.GetBool(t...) { //说明到了回合结束取消节点,那么就将变化过的属性变化回来 if !e.GetBool(t...) && e.Hit() { //说明到了回合结束取消节点,那么就将变化过的属性变化回来
//还原属性 //还原属性
e.Ctx().Our.CurrentPet.PetInfo.Type = e.oldtype e.Ctx().Our.CurrentPet.PetInfo.Type = e.oldtype
} }

View File

@@ -36,7 +36,7 @@ func init() {
func registerConditionalAddDamageEffects() { func registerConditionalAddDamageEffects() {
// 效果ID与条件函数的映射 // 效果ID与条件函数的映射
effectMap := map[int]conditionFunc{ effectMap := map[int]conditionFunc{
130: conditionIsTypeX, // 对方是X属性时 // 130: conditionIsTypeX, // 对方是X属性时
133: conditionIsBurned, // 对方烧伤时 133: conditionIsBurned, // 对方烧伤时
141: conditionIsFrozen, // 对方冻伤时 141: conditionIsFrozen, // 对方冻伤时

View File

@@ -2,6 +2,7 @@ package effect
import ( import (
element "blazing/common/data/Element" element "blazing/common/data/Element"
"blazing/common/utils"
"blazing/logic/service/fight/action" "blazing/logic/service/fight/action"
"blazing/logic/service/fight/info" "blazing/logic/service/fight/info"
"blazing/logic/service/fight/input" "blazing/logic/service/fight/input"
@@ -81,6 +82,16 @@ func (e *ContinuousDamage) calculateDamage() decimal.Decimal {
Div(decimal.NewFromInt(8)) Div(decimal.NewFromInt(8))
} }
type Burned struct {
ContinuousDamage //继承扣血类
}
func (e *Burned) Skill_Hit() bool {
e.Ctx().SkillEntity.Power /= 2
return true
}
// 寄生种子状态:扣血同时给对方回血 // 寄生种子状态:扣血同时给对方回血
type ParasiticSeed struct { type ParasiticSeed struct {
BaseStatus BaseStatus
@@ -107,13 +118,105 @@ func (e *ParasiticSeed) Skill_Hit_Pre_ex(attacker, defender *action.SelectSkillA
return true return true
} }
type Flammable struct {
BaseStatus
}
func (e *Flammable) Skill_Hit_Pre(fattack *action.SelectSkillAction, sattack *action.SelectSkillAction) bool {
if e.Ctx().SkillEntity == nil {
return true
}
e.Ctx().SkillEntity.Accuracy -= 30
return true
}
func (e *Flammable) Skill_Use_ex() bool {
if e.Ctx().SkillEntity == nil {
return true
}
if e.Ctx().SkillEntity.Type().Secondary != nil {
return true
}
if e.Ctx().SkillEntity.Type().Primary != element.ElementTypeFire {
return true
}
// 获取状态效果
eff := input.Geteffect(input.EffectType.Status, info.PetStatus.Burned)
if eff == nil {
return true
}
duration := int(e.Input.FightC.GetRand().Int31n(2)) // 默认随机 2~3 回合
eff.Duration(duration)
eff.SetArgs(e.Ctx().Our) //输入参数是对方
e.Ctx().Our.AddEffect(e.Ctx().Our, eff)
return true
}
type Confused struct {
BaseStatus
}
func (e *Confused) Skill_Hit_Pre(fattack *action.SelectSkillAction, sattack *action.SelectSkillAction) bool {
if e.Ctx().SkillEntity == nil {
return true
}
e.Ctx().SkillEntity.Accuracy -= 80
ok, _, _ := e.Input.Player.Roll(5, 100)
if !ok {
return true
}
e.Ctx().Our.Damage(e.Ctx().Our, &info.DamageZone{
Type: info.DamageType.Fixed,
Damage: decimal.NewFromInt(50),
})
return true
}
type Weakened struct {
BaseStatus
}
func (e *Weakened) Damage_DIV_ex(t *info.DamageZone) bool {
// 1. 定义衰弱等级对应的倍率表索引对应等级0级无倍率
// 索引0: 0%未衰弱、1:25%、2:50%、3:100%、4:250%、5:500%
weakenedMultiples := []decimal.Decimal{
decimal.NewFromInt(0), // 0级
decimal.NewFromFloat(0.25), // 1级25%
decimal.NewFromFloat(0.5), // 2级50%
decimal.NewFromFloat(1.0), // 3级100%
decimal.NewFromFloat(2.5), // 4级250%
decimal.NewFromFloat(5.0), // 5级500%
}
// 2. 校验并限制衰弱等级≤0 直接返回≥5 按5级算
level := e.Status
if level <= 0 {
return true
}
// 等级上限限制为5避免越界
level = utils.Min(level, 5)
// 3. 获取对应等级的倍率,计算最终伤害
multiple := weakenedMultiples[level]
// 伤害计算公式:原伤害 + 原伤害 × 倍率(等价于 原伤害 × (1+倍率)
t.Damage = t.Damage.Add(t.Damage.Mul(multiple))
return true
}
func init() { func init() {
// 注册持续伤害类状态 // 注册持续伤害类状态
input.InitEffect(input.EffectType.Status, int(info.PetStatus.DrainedHP), &ParasiticSeed{}) // 寄生种子 input.InitEffect(input.EffectType.Status, int(info.PetStatus.DrainedHP), &ParasiticSeed{}) // 寄生种子
input.InitEffect(input.EffectType.Status, int(info.PetStatus.Poisoned), &ContinuousDamage{}) // 中毒 input.InitEffect(input.EffectType.Status, int(info.PetStatus.Poisoned), &ContinuousDamage{}) // 中毒
input.InitEffect(input.EffectType.Status, int(info.PetStatus.Frozen), &ContinuousDamage{}) // 冻伤 input.InitEffect(input.EffectType.Status, int(info.PetStatus.Frozen), &ContinuousDamage{}) // 冻伤
input.InitEffect(input.EffectType.Status, int(info.PetStatus.Burned), &ContinuousDamage{}) // 烧伤 input.InitEffect(input.EffectType.Status, int(info.PetStatus.Burned), &Burned{}) // 烧伤
input.InitEffect(input.EffectType.Status, int(info.PetStatus.Weakened), &Weakened{}) // 衰弱
input.InitEffect(input.EffectType.Status, int(info.PetStatus.Confused), &Confused{}) // 混乱
input.InitEffect(input.EffectType.Status, int(info.PetStatus.Flammable), &Flammable{}) // 易燃
// 批量注册不能行动的状态 // 批量注册不能行动的状态
nonActingStatuses := []info.EnumPetStatus{ nonActingStatuses := []info.EnumPetStatus{
info.PetStatus.Paralysis, // 麻痹 info.PetStatus.Paralysis, // 麻痹

View File

@@ -32,13 +32,14 @@ func registerStatusEffects() {
10: info.PetStatus.Paralysis, 10: info.PetStatus.Paralysis,
11: info.PetStatus.Poisoned, 11: info.PetStatus.Poisoned,
12: info.PetStatus.Burned, 12: info.PetStatus.Burned,
//13: info.PetStatus.DrainedHP,
14: info.PetStatus.Frozen, 14: info.PetStatus.Frozen,
15: info.PetStatus.Fear, 15: info.PetStatus.Fear,
16: info.PetStatus.Sleep, 16: info.PetStatus.Sleep,
22: info.PetStatus.Tired, 22: info.PetStatus.Tired,
94: info.PetStatus.Petrified, 94: info.PetStatus.Petrified,
99: info.PetStatus.Confused, 99: info.PetStatus.Confused,
103: info.PetStatus.Weakened,
114: info.PetStatus.Flammable, 114: info.PetStatus.Flammable,
} }

View File

@@ -28,13 +28,8 @@ func (f *FightC) processSkillAttack(attacker, defender *input.Input, a *info.Ski
}) })
attacker.AttackValue.AttackTime = a.AttackTime //是否命中赋值 attacker.AttackValue.AttackTime = a.AttackTime //是否命中赋值
// attacker.Exec(func(t input.Effect) bool { //计算命中 miss改命中 var oldprop [2][6]int8
// //计算视为效果 oldprop[0], oldprop[1] = attacker.Prop, defender.Prop //先复制能力提升
// t.Ctx().SkillEntity = a
// t.Calculate_Pre() //相当于先调整基础命中,不光调整命中,这里还能调整技能属性,暴击率
// return true
// })
attacker.Exec(func(t input.Effect) bool { attacker.Exec(func(t input.Effect) bool {
//计算变威力 //计算变威力
t.Ctx().SkillEntity = a t.Ctx().SkillEntity = a
@@ -55,20 +50,21 @@ func (f *FightC) processSkillAttack(attacker, defender *input.Input, a *info.Ski
attacker.DamageZone.Damage = attacker.CalculatePower(defender, a) attacker.DamageZone.Damage = attacker.CalculatePower(defender, a)
//睡眠受击消除 //睡眠受击消除
if attacker.IsCritical == 1 { }
//暴击破防 attacker.Prop, defender.Prop = oldprop[0], oldprop[1] //先复制能力提升
if a.Category() == info.Category.PHYSICAL && defender.Prop[1] > 0 {
defender.Prop[1] = 0 if attacker.IsCritical == 1 { //命中了才有暴击
} else if a.Category() == info.Category.SPECIAL && defender.Prop[3] > 0 { //暴击破防
defender.Prop[3] = 0 if a.Category() == info.Category.PHYSICAL && defender.Prop[1] > 0 {
}
//暴击翻倍
attacker.DamageZone.Damage = attacker.DamageZone.Damage.Mul(decimal.NewFromInt(2))
defender.Prop[1] = 0
} else if a.Category() == info.Category.SPECIAL && defender.Prop[3] > 0 {
defender.Prop[3] = 0
} }
//暴击翻倍
attacker.DamageZone.Damage = attacker.DamageZone.Damage.Mul(decimal.NewFromInt(2))
} }
// attacker.AddEffects(attacker.EffectCache...) //命中再添加效果 // attacker.AddEffects(attacker.EffectCache...) //命中再添加效果
for _, e := range attacker.EffectCache { for _, e := range attacker.EffectCache {
@@ -337,8 +333,9 @@ func (f *FightC) enterturn(fattack, sattack *action.SelectSkillAction) {
} }
} }
f.Switch = []*action.ActiveSwitchAction{}
ff.Player.SendPackCmd(2505, &ret) ff.Player.SendPackCmd(2505, &ret)
}) })
f.Switch = []*action.ActiveSwitchAction{}
} }

View File

@@ -103,12 +103,16 @@ func (f *FightC) LoadPercent(c common.PlayerI, percent int32) {
} }
func (f *FightC) initplayer(c common.PlayerI) *input.Input { func (f *FightC) initplayer(c common.PlayerI) (*input.Input, errorcode.ErrorCode) {
if len(c.GetInfo().PetList) == 0 { if len(c.GetInfo().PetList) == 0 {
return nil return nil, 0
} }
if !c.CanFight() {
return nil, errorcode.ErrorCodes.ErrNoEligiblePokemon
}
in := input.NewInput(f, c) in := input.NewInput(f, c)
in.AllPet = make([]*info.BattlePetEntity, 0) in.AllPet = make([]*info.BattlePetEntity, 0)
in.InitAttackValue() in.InitAttackValue()
@@ -127,6 +131,7 @@ func (f *FightC) initplayer(c common.PlayerI) *input.Input {
for _, v := range RandomElfIDs(3) { for _, v := range RandomElfIDs(3) {
p := model.GenPetInfo(v, 24, -1, -1, -1, 100) p := model.GenPetInfo(v, 24, -1, -1, -1, 100)
p.CatchTime = uint32(v) p.CatchTime = uint32(v)
p.Update()
in.AllPet = append(in.AllPet, info.CreateBattlePetEntity(*p, f.rand)) in.AllPet = append(in.AllPet, info.CreateBattlePetEntity(*p, f.rand))
} }
@@ -135,7 +140,7 @@ func (f *FightC) initplayer(c common.PlayerI) *input.Input {
} }
in.CurrentPet = in.AllPet[0] in.CurrentPet = in.AllPet[0]
return in return in, 0
} }
// RandomElfIDs 从1-2000中随机抽取n个不重复的精灵ID // RandomElfIDs 从1-2000中随机抽取n个不重复的精灵ID
@@ -194,8 +199,18 @@ func NewFight(p1, p2 common.PlayerI, fn func(*info.FightOverInfo)) (*FightC, err
//这里应该挪到玩家初始化执行 //这里应该挪到玩家初始化执行
f.ReadyInfo.Status = f.Info.Status f.ReadyInfo.Status = f.Info.Status
var err errorcode.ErrorCode
f.Our, err = f.initplayer(p1)
if err > 0 {
return nil, err
f.Our, f.Opp = f.initplayer(p1), f.initplayer(p2) }
f.Opp, err = f.initplayer(p2)
if err > 0 {
return nil, err
}
f.ReadyInfo.OurInfo, f.ReadyInfo.OurPetList = initfightready(f.Our) f.ReadyInfo.OurInfo, f.ReadyInfo.OurPetList = initfightready(f.Our)
f.ReadyInfo.OpponentInfo, f.ReadyInfo.OpponentPetList = initfightready(f.Opp) f.ReadyInfo.OpponentInfo, f.ReadyInfo.OpponentPetList = initfightready(f.Opp)
@@ -217,9 +232,17 @@ func NewFight(p1, p2 common.PlayerI, fn func(*info.FightOverInfo)) (*FightC, err
ff.Player.SendPackCmd(2503, &f.ReadyInfo) ff.Player.SendPackCmd(2503, &f.ReadyInfo)
}) })
// 60秒后判断战斗是否开始
cool.Cron.AfterFunc(60*time.Second, func() { var t time.Duration
// 60秒后判断战斗是否开始
switch f.Info.Status {
case info.BattleMode.PET_MELEE:
t = 120 * time.Second
default:
t = 60 * time.Second
}
cool.Cron.AfterFunc(t, func() {
if !f.Our.Finished || !f.Opp.Finished { //如果有任一没有加载完成 if !f.Our.Finished || !f.Opp.Finished { //如果有任一没有加载完成
f.closefight = true //阻止继续添加action f.closefight = true //阻止继续添加action
f.Reason = info.BattleOverReason.PlayerOVerTime f.Reason = info.BattleOverReason.PlayerOVerTime

View File

@@ -29,16 +29,17 @@ func InitEffect(etype EnumEffectType, id int, t Effect) {
NodeM[id+int(etype)] = t NodeM[id+int(etype)] = t
} }
func Geteffect(etype EnumEffectType, id int) Effect { func Geteffect[T int | byte](etype EnumEffectType, id T) Effect {
//todo 获取前GetEffect //todo 获取前GetEffect
ret, ok := NodeM[id+int(etype)] ret, ok := NodeM[int(id)+int(etype)]
if ok { if ok {
//todo 获取前GetEffect //todo 获取前GetEffect
eff := deep.MustCopy(ret) eff := deep.MustCopy(ret)
if eff.ID() >= int(EffectType.Status) && eff.ID() < int(EffectType.Sub) { if eff.ID() >= int(EffectType.Status) && eff.ID() < int(EffectType.Sub) {
eff.CanStack(true) //状态类不能被覆盖,只能无限叠加 eff.CanStack(true) //状态类不能被覆盖,只能无限叠加
} }
return eff return eff
@@ -150,6 +151,7 @@ func (our *Input) AddEffect(in *Input, e Effect) Effect {
return nil return nil
} }
e.Alive(true) //添加后默认激活 e.Alive(true) //添加后默认激活
//todo 免疫 //todo 免疫
//TODO 先激活 //TODO 先激活
@@ -212,9 +214,9 @@ func (our *Input) Exec(fn func(Effect) bool) bool {
} }
// 消除回合类效果 efftype 输入是消对方的还是自己的,false是自己,true是对方 // 消除回合类效果 efftype 输入是消对方的还是自己的,false是自己,true是对方
func (our *Input) CancelTurn() { func (our *Input) CancelTurn(in *Input) {
for _, value := range our.Effects { for _, value := range our.Effects {
if value.Duration() > 0 { //false是自身,true是对方,反转后为真就是自己的 if value.Duration() > 0 && value.Alive() { //false是自身,true是对方,反转后为真就是自己的
//slice = append(slice[:i], slice[i+1:]...) //slice = append(slice[:i], slice[i+1:]...)
value.Alive(false) value.Alive(false)
} }

View File

@@ -71,8 +71,8 @@ func (f *FightC) battleLoop() {
//待退出玩家战斗状态 //待退出玩家战斗状态
}) })
close(f.actionChan) //close(f.actionChan)
//fmt.Println("战斗循环结束") fmt.Println("战斗循环结束")
close(f.over) close(f.over)
} }

View File

@@ -40,3 +40,7 @@ func (f *AI_player) InvitePlayer(ff common.PlayerI) {
func (p *AI_player) QuitFight() { func (p *AI_player) QuitFight() {
} }
func (p *AI_player) CanFight() bool {
return true
}

View File

@@ -28,7 +28,11 @@ func (p *Player) InvitePlayerToBattle() {
}) })
} }
func (p *Player) JoinFight(fn func(p common.PlayerI) bool) { func (p *Player) JoinFight(fn func(p common.PlayerI) bool) errorcode.ErrorCode {
if !p.CanFight() {
return errorcode.ErrorCodes.ErrNoEligiblePokemon
}
p.GetSpace().User.Range(func(key uint32, v common.PlayerI) bool { p.GetSpace().User.Range(func(key uint32, v common.PlayerI) bool {
@@ -39,14 +43,18 @@ func (p *Player) JoinFight(fn func(p common.PlayerI) bool) {
// p.Fightinfo = nil //先将自身的准备信息置空 // p.Fightinfo = nil //先将自身的准备信息置空
// //value.PVPinfo = nil // //value.PVPinfo = nil
ttt := fn(v)
if ttt {
}
// lw = value // lw = value
return fn(v) //如果发起成功就停止,否则继续遍历队列 return ttt //如果发起成功就停止,否则继续遍历队列
} }
} }
return false return false
}) })
return 0
} }
func (p *Player) SendLoadPercent(b info.LoadPercentOutboundInfo) { func (p *Player) SendLoadPercent(b info.LoadPercentOutboundInfo) {
@@ -68,9 +76,7 @@ func (p *Player) SendLoadPercent(b info.LoadPercentOutboundInfo) {
// common.PlayerI对应的邀请者玩家成功时有效 // common.PlayerI对应的邀请者玩家成功时有效
func (p *Player) AgreeBattle(userid, flag uint32, fn func(p common.PlayerI) bool) errorcode.ErrorCode { func (p *Player) AgreeBattle(userid, flag uint32, fn func(p common.PlayerI) bool) errorcode.ErrorCode {
// 处理完毕后清空收到的邀请列表原defer逻辑保留 // 处理完毕后清空收到的邀请列表原defer逻辑保留
defer func() {
p.HavePVPinfo = make([]common.PlayerI, 0)
}()
resp := &info.S2C_NOTE_HANDLE_FIGHT_INVITE{ resp := &info.S2C_NOTE_HANDLE_FIGHT_INVITE{
UserID: p.Info.UserID, UserID: p.Info.UserID,
Nick: p.Info.Nick, Nick: p.Info.Nick,
@@ -80,13 +86,15 @@ func (p *Player) AgreeBattle(userid, flag uint32, fn func(p common.PlayerI) bool
if o != nil && o.Getfightinfo() == p.Fightinfo { if o != nil && o.Getfightinfo() == p.Fightinfo {
resp.Result = flag resp.Result = flag
// 检查邀请者的邀请是否有效(对方已取消邀请) // 检查邀请者的邀请是否有效(对方已取消邀请)
if atomic.LoadUint32(&o.GetInfo().UserID) == 0 { if o.Getfightinfo().PlayerID == 0 {
resp.Result = 4 // 邀请已取消 resp.Result = 4 // 邀请已取消
} }
//把不在线和不满足出战要求放到战斗检测里 //把不在线和不满足出战要求放到战斗检测里
//flasg本身就是00 //flasg本身就是00
if fn(o) { //发起成功 if fn(o) { //发起成功
p.HavePVPinfo = make([]common.PlayerI, 0)
return -1 //返回 return -1 //返回
} else { //发起失败 } else { //发起失败
resp.Result = 3 resp.Result = 3

View File

@@ -17,7 +17,7 @@ func NewPlayer(opts ...PlayerOption) *Player {
p.monsters = generateThreeUniqueNumbers() p.monsters = generateThreeUniqueNumbers()
p.Done = NewDone(p) //发布订阅事件 p.Done = NewDone(p) //发布订阅事件
p.StopChan = cool.Cron.ScheduleFunc(10*time.Second, func() { p.StopChan = cool.Cron.ScheduleFunc(10*time.Second, func() {
if p.Canmon && p.FightC == nil { //已经进入地图或者没在战斗中,就可以刷新怪 if p.Canmon != 0 { //已经进入地图或者没在战斗中,就可以刷新怪
p.SpawnMonsters() p.SpawnMonsters()
} }
}) })

View File

@@ -63,8 +63,9 @@ type Player struct {
// PVP被邀请信息 // PVP被邀请信息
HavePVPinfo []common.PlayerI HavePVPinfo []common.PlayerI
monsters [3]int monsters [3]int
Canmon bool //可以刷怪 //0 无,1可以刷怪,2是切换过地图
Changemap bool //是否切换过地图 Canmon uint32 //可以刷怪
// Changemap bool //是否切换过地图
} }
// PlayerOption 定义配置 Player 的函数类型 // PlayerOption 定义配置 Player 的函数类型
@@ -98,6 +99,8 @@ func (p *Player) Getfightinfo() info.Fightinfo {
func (p *Player) QuitFight() { func (p *Player) QuitFight() {
//将战斗标记设置为0 这里的标记是 //将战斗标记设置为0 这里的标记是
atomic.StoreUint32(&p.Fightinfo.Status, 0) atomic.StoreUint32(&p.Fightinfo.Status, 0)
atomic.StoreUint32(&p.Fightinfo.PlayerID, 0)
p.FightC = nil
} }
func (p *Player) GetSpace() *space.Space { func (p *Player) GetSpace() *space.Space {
@@ -105,30 +108,30 @@ func (p *Player) GetSpace() *space.Space {
} }
// 0无战斗1PVP2,BOOS,3PVE // 0无战斗1PVP2,BOOS,3PVE
func (p *Player) CanFight(staus uint32) bool { func (p *Player) CanFight() bool {
if atomic.CompareAndSwapUint32(&p.Fightinfo.Status, 0, staus) { //先判断是否竞态条件被挑战 // if atomic.CompareAndSwapUint32(&p.Fightinfo.Status, 0, staus) { //先判断是否竞态条件被挑战
//成功,继续判断 //成功,继续判断
if p.FightC != nil {
return false
}
// if p.GetSpace().ARENA.ChallengerID == p.Info.UserID || p.GetSpace().ARENA.Id == p.Info.UserID {
// return false
// }
for _, v := range p.Info.PetList {
if v.Hp > 0 { // 只要找到一个血量大于0的宠物就可以战斗
return true
}
}
// 遍历完所有宠物都没有血量大于0的才不能战斗
if p.FightC != nil {
return false return false
} }
// if p.GetSpace().ARENA.ChallengerID == p.Info.UserID || p.GetSpace().ARENA.Id == p.Info.UserID {
// return false
// }
for _, v := range p.Info.PetList {
if v.Hp > 0 { // 只要找到一个血量大于0的宠物就可以战斗
return true
}
}
// 遍历完所有宠物都没有血量大于0的才不能战斗
return false return false
// }
// return false
} }
@@ -190,17 +193,15 @@ func (p *Player) genMonster(mapid uint32) {
} }
if p.Changemap { if atomic.CompareAndSwapUint32(&p.Canmon, 2, 1) {
p.Changemap = false
p.OgreInfo = OgreInfo{} //切地图清空 p.OgreInfo = OgreInfo{} //切地图清空
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
p.OgreInfo.Data[p.monsters[i]] = t1.Data[p.monsters[i]] p.OgreInfo.Data[p.monsters[i]] = t1.Data[p.monsters[i]]
} }
} else {
p.OgreInfo.Data[oldnum] = OgrePetInfo{}
p.OgreInfo.Data[newNum] = t1.Data[newNum]
} }
p.OgreInfo.Data[oldnum] = OgrePetInfo{}
p.OgreInfo.Data[newNum] = t1.Data[newNum]
} }

View File

@@ -7,3 +7,13 @@ type ARENA struct {
HostWins uint32 // 应该是擂台人的连胜数 HostWins uint32 // 应该是擂台人的连胜数
ChallengerID uint32 // 挑战者的userid ChallengerID uint32 // 挑战者的userid
} }
func (t *ARENA) Reset() {
t.Flag = 0
t.UserID = 0
t.Nick = ""
t.HostWins = 0
t.ChallengerID = 0
}

View File

@@ -15,31 +15,27 @@ var mappool, _ = ants.NewPool(-1)
// 向其他人广播,不含自己 // 向其他人广播,不含自己
func (s *Space) Broadcast(c common.PlayerI, cmd uint32, data any) { func (s *Space) Broadcast(c common.PlayerI, cmd uint32, data any) {
mappool.Submit(func() { //mappool.Submit(func() {
s.User.Range(func(k uint32, v common.PlayerI) (stop bool) { s.User.Range(func(k uint32, v common.PlayerI) (stop bool) {
if k != c.GetInfo().UserID { if k != c.GetInfo().UserID {
v.SendPackCmd(cmd, data) v.SendPackCmd(cmd, data)
} }
return false return false
})
}) })
//})
} }
func (s *Space) LeaveMap(c common.PlayerI) { func (s *Space) LeaveMap(c common.PlayerI) {
if c == nil {
return
}
if c.GetInfo() == nil {
return
}
if atomic.CompareAndSwapUint32(&s.Owner.UserID, c.GetInfo().UserID, 0) { if atomic.CompareAndSwapUint32(&s.Owner.UserID, c.GetInfo().UserID, 0) {
s.ARENA_Player = nil s.ARENA_Player = nil
s.Owner.Reset()
s.Broadcast(c, 2419, &s.Owner) s.Broadcast(c, 2419, &s.Owner)
} }

View File

@@ -48,3 +48,32 @@ type ChangeNONOColorOutboundInfo struct {
Color uint32 `codec:"color"` // 前端发来的更改的颜色rgb值 (对应Java的@UInt long) Color uint32 `codec:"color"` // 前端发来的更改的颜色rgb值 (对应Java的@UInt long)
} }
// C2SDanceAction 前端发送给后端的舞蹈动作请求结构体
// 对应 C# 的 C2S_DANCE_ACTION
type C2SDanceAction struct {
Head common.TomeeHeader `cmd:"2103" struc:"[0]pad"`
// Reserve 固定参数(值为 10001
Reserve uint32 `struc:"uint32,big"`
// Type 动作类型3 = 蹲下)
Type uint32 `struc:"uint32,big"`
}
type S2CDanceAction struct {
// UserID 玩家ID
UserID uint32 `struc:"uint32,big"`
// Type 动作类型(透传前端的 type3 = 蹲下)
Type uint32 `struc:"uint32,big"`
}
type C2SPEOPLE_TRANSFROM struct {
Head common.TomeeHeader `cmd:"2111" struc:"[0]pad"`
// Reserve 固定参数(值为 10001
SuitID uint32 `struc:"uint32,big"`
}
type S2CPEOPLE_TRANSFROM struct {
// UserID 玩家ID
UserID uint32 `struc:"uint32,big"`
// Type 动作类型(透传前端的 type3 = 蹲下)
SuitID uint32 `struc:"uint32,big"`
}

View File

@@ -108,22 +108,49 @@ func (pet *PetInfo) Cure() {
} }
// 传入bool则不升级 // 传入bool则不升级
// Update 改造为循环进化:直到宠物无法再进化为止,再更新经验
// t ...bool原参数逻辑len(t)==0时触发进化检查否则仅更新经验
func (petinfo *PetInfo) Update(t ...bool) { func (petinfo *PetInfo) Update(t ...bool) {
// 仅在无额外参数时触发循环进化逻辑
basic := xmlres.PetMAP[int(petinfo.ID)]
if len(t) == 0 { if len(t) == 0 {
// 检查是否可以进化 // 最大进化次数限制(防止配置表闭环导致死循环)
if basic.EvolvesTo != 0 && // 有明确的进化 maxEvolveTimes := 10
int(petinfo.Level) >= basic.EvolvingLv && // 有明确的进化等级 evolveCount := 0
basic.IsLarge == 0 { // 非最终形态
// 循环进化:直到不满足进化条件 或 达到最大进化次数
for {
// 防止死循环,超出次数直接退出
if evolveCount >= maxEvolveTimes {
break
}
// 获取当前宠物形态的配置
basic, ok := xmlres.PetMAP[int(petinfo.ID)]
// 配置不存在,直接退出循环
if !ok {
break
}
// 检查是否满足进化条件
canEvolve := basic.EvolvesTo != 0 && // 有明确的进化目标
int(petinfo.Level) >= basic.EvolvingLv && // 等级达到进化要求
basic.IsLarge == 0 // 非最终形态
// 不满足进化条件,退出循环
if !canEvolve {
break
}
// 执行进化更新宠物ID为进化后形态
petinfo.ID = uint32(basic.EvolvesTo) petinfo.ID = uint32(basic.EvolvesTo)
basic = xmlres.PetMAP[int(petinfo.ID)] //重新计算 evolveCount++ // 进化次数+1
} }
} }
// 进化完成后,统一更新经验(原逻辑保留)
petinfo.LvExp = petinfo.NextLvExp petinfo.LvExp = petinfo.NextLvExp
// 获取最终形态的宠物配置,计算下一等级经验
basic := xmlres.PetMAP[int(petinfo.ID)]
petinfo.NextLvExp = calculateExperience(petinfo.Level, basic.GetBasic()) petinfo.NextLvExp = calculateExperience(petinfo.Level, basic.GetBasic())
} }

View File

@@ -114,6 +114,7 @@ type PlayerInfo struct {
PetMaxLevel uint32 `struc:"uint32" json:"pet_max_level"` // 精灵最高等级 PetMaxLevel uint32 `struc:"uint32" json:"pet_max_level"` // 精灵最高等级
AllPetNumber uint32 `struc:"uint32" json:"all_pet_number"` // 精灵数量 AllPetNumber uint32 `struc:"uint32" json:"all_pet_number"` // 精灵数量
MonKingWin uint32 `struc:"uint32" json:"mon_king_win"` // 精灵王胜场 MonKingWin uint32 `struc:"uint32" json:"mon_king_win"` // 精灵王胜场
MessWin uint32 `struc:"skip" json:"mess_win"` // 大乱斗胜场
CurrentStage uint32 `struc:"uint32" json:"current_stage"` // 勇者之塔层数 CurrentStage uint32 `struc:"uint32" json:"current_stage"` // 勇者之塔层数
MaxStage uint32 `struc:"uint32" json:"max_stage"` // 试炼之塔最高层 MaxStage uint32 `struc:"uint32" json:"max_stage"` // 试炼之塔最高层
CurrentFreshStage uint32 `struc:"uint32" json:"current_fresh_stage"` // 当前试炼层数 CurrentFreshStage uint32 `struc:"uint32" json:"current_fresh_stage"` // 当前试炼层数