Files
bl/logic/service/player/player.go

524 lines
13 KiB
Go
Raw Normal View History

package player
import (
"blazing/common/data"
2026-02-23 04:01:57 +08:00
"blazing/common/data/xmlres"
"blazing/common/socket/errorcode"
"blazing/cool"
2025-11-18 23:41:31 +00:00
"blazing/logic/service/common"
2025-11-15 22:17:43 +00:00
"blazing/logic/service/fight/info"
"blazing/logic/service/space"
"fmt"
"sync"
2025-11-18 22:16:55 +00:00
"sync/atomic"
"time"
"blazing/modules/base/service"
config "blazing/modules/config/service"
2026-01-21 20:46:05 +00:00
dictrvice "blazing/modules/dict/service"
blservice "blazing/modules/player/service"
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
2026-01-23 14:59:15 +00:00
csmap "github.com/mhmtszr/concurrent-swiss-map"
"github.com/panjf2000/gnet/v2"
)
// Mainplayer 全局玩家数据存储映射
var Mainplayer = csmap.New[uint32, *ClientData]()
type OgrePetInfo struct {
2026-02-08 17:57:42 +08:00
ID uint32
ShinyLen uint32 `json:"-" struc:"sizeof=ShinyInfo"`
ShinyInfo []data.GlowFilter `json:"ShinyInfo,omitempty"`
Lv uint32 `struc:"skip"` //等级
//XUANCAI int64 `struc:"skip"`
//Item int64 `struc:"skip"` //奖励,如果有的话
Ext uint32 `struc:"skip"` //是否变尼尔尼奥
IsCapture int `struc:"skip"`
}
2026-02-26 19:22:04 +08:00
func (o *OgrePetInfo) GetID() int {
if o.Ext != 0 {
return int(o.Ext)
}
return int(o.ID)
}
func (o *OgrePetInfo) GetLevel() int {
if o.Ext != 0 {
return 16
}
return int(o.Lv)
}
// handleNPCFightSpecial 处理NPC战斗特殊情况
func (o *OgrePetInfo) HandleNPCFightSpecial(can int) {
if can == 0 && o.Ext == 0 { //不能抓并且不是尼尔
o.IsCapture = 0
return
}
petCfg, ok := xmlres.PetMAP[o.GetID()]
if !ok {
o.IsCapture = 0
} else {
o.IsCapture = gconv.Int(petCfg.CatchRate)
}
if o.Ext != 0 {
return
}
var co *data.GlowFilter
if cool.Config.ServerInfo.IsVip != 0 { //测试服,百分百异色
co = config.NewShinyService().RandShiny(o.ID)
} else {
if o.IsCapture != 0 && grand.Meet(1, 500) {
co = config.NewShinyService().RandomByWeightShiny(o.ID)
}
}
if co != nil && len(o.ShinyInfo) == 0 {
o.ShinyInfo = append(o.ShinyInfo, *co)
}
}
type Player struct {
MainConn gnet.Conn
baseplayer
IsLogin bool //是否登录
2026-03-12 14:35:27 +08:00
IsJoin bool //是否加入RPC对局 用重连函数写临时缓存join重连后统一join
2025-11-16 20:30:17 +00:00
Done
MapNPC *time.Timer
context.Context
Fightinfo info.Fightinfo // 当前邀请的玩家ID
2025-11-18 22:16:55 +00:00
Logintime uint32 // 当前登录时间
OgrePet
PetDisplay PetDisplayState
Service *blservice.UserService
User *service.BaseSysUserService
// PVP被邀请信息
2025-11-18 23:41:31 +00:00
HavePVPinfo []common.PlayerI
monsters [3]int
// 0 无,1可以刷怪,2是切换过地图
Canmon uint32 // 可以刷怪
CurDark uint32
2026-02-08 02:11:46 +08:00
Hash uint32
ArenaFlags uint32
logoutMu sync.Mutex
logoutDone chan struct{}
logoutSaved bool
}
const (
ArenaFlagNoSwitchPet uint32 = 1 << iota
ArenaFlagNoHeal
ArenaFlagNoPVP
)
const arenaHostFlagMask = ArenaFlagNoSwitchPet | ArenaFlagNoHeal | ArenaFlagNoPVP
func (p *Player) addArenaFlag(mask uint32) {
for {
current := atomic.LoadUint32(&p.ArenaFlags)
next := current | mask
if current == next || atomic.CompareAndSwapUint32(&p.ArenaFlags, current, next) {
return
}
}
}
func (p *Player) clearArenaFlag(mask uint32) {
for {
current := atomic.LoadUint32(&p.ArenaFlags)
next := current &^ mask
if current == next || atomic.CompareAndSwapUint32(&p.ArenaFlags, current, next) {
return
}
}
}
func (p *Player) HasArenaFlag(mask uint32) bool {
return atomic.LoadUint32(&p.ArenaFlags)&mask != 0
}
func (p *Player) SetArenaHostFlags() {
p.addArenaFlag(arenaHostFlagMask)
}
func (p *Player) ClearArenaHostFlags() {
p.clearArenaFlag(arenaHostFlagMask)
}
func (p *Player) IsArenaHost() bool {
return p.HasArenaFlag(arenaHostFlagMask)
}
func (p *Player) IsArenaSwitchLocked() bool {
return p.HasArenaFlag(ArenaFlagNoSwitchPet)
}
func (p *Player) IsArenaHealLocked() bool {
return p.HasArenaFlag(ArenaFlagNoHeal)
}
func (p *Player) IsArenaPVPLocked() bool {
return p.HasArenaFlag(ArenaFlagNoPVP)
}
type OgrePet struct {
Data [9]OgrePetInfo
}
func (p *Player) GetCoins(amount int64) bool {
if int64(p.Info.Coins) < amount {
return false
}
return true
}
2026-02-10 22:09:15 +08:00
func (p *Player) UseGold(amount int64) bool {
if p.User.GetGold(uint(p.Info.UserID)) < amount {
return false
}
return true
}
func (p *Player) GetAction() {
2025-11-18 23:41:31 +00:00
}
// InvitePlayer 邀请玩家进行对战
2025-11-18 23:41:31 +00:00
func (f *Player) InvitePlayer(ff common.PlayerI) {
f.HavePVPinfo = append(f.HavePVPinfo, ff)
tt := common.NewTomeeHeader(2501, f.GetInfo().UserID)
f.SendPack(tt.Pack(&info.NoteInviteToFightOutboundInfo{
UserID: ff.GetInfo().UserID,
Nick: ff.GetInfo().Nick,
Mode: ff.Getfightinfo().Mode,
}))
}
// Getfightinfo 获取玩家的战斗信息
2025-11-18 22:16:55 +00:00
func (p *Player) Getfightinfo() info.Fightinfo {
2025-11-18 23:41:31 +00:00
return p.Fightinfo
2025-11-16 20:30:17 +00:00
}
// QuitFight 退出战斗
func (p *Player) QuitFight() {
p.FightC = nil
atomic.StoreUint32(&p.Fightinfo.Mode, 0)
2025-11-19 00:09:12 +00:00
}
// GetSpace 获取玩家所在的空间
func (p *Player) GetSpace() *space.Space {
return space.GetSpace(p.Info.MapID)
}
2026-04-15 00:07:36 +08:00
func (p *Player) CurrentMapPetLevelLimit() uint32 {
if p == nil {
return 100
}
currentSpace := p.GetSpace()
if currentSpace != nil && currentSpace.IsLevelBreakMap {
return 0
}
return 100
}
2026-04-15 03:22:59 +08:00
func (p *Player) IsCurrentMapLevelBreak() bool {
return p != nil && p.CurrentMapPetLevelLimit() == 0
}
// CanFight 检查玩家是否可以进行战斗
2025-11-19 00:09:12 +00:00
// 0无战斗1PVP2,BOOS,3PVE
func (p *Player) CanFight() errorcode.ErrorCode {
if len(p.Info.PetList) == 0 {
atomic.StoreUint32(&p.Fightinfo.Mode, 0)
return errorcode.ErrorCodes.ErrBattleCancelled
}
2025-11-18 22:16:55 +00:00
if p.FightC != nil {
atomic.StoreUint32(&p.Fightinfo.Mode, 0)
return errorcode.ErrorCodes.ErrBattleEnded
}
for _, pet := range p.Info.PetList {
if pet.Hp > 0 { // 只要找到一个血量大于0的宠物就可以战斗
return 0
2025-11-18 22:16:55 +00:00
}
}
// 遍历完所有宠物都没有血量大于0的才不能战斗
atomic.StoreUint32(&p.Fightinfo.Mode, 0)
return errorcode.ErrorCodes.ErrPokemonNoStamina
}
func (p *Player) SendPack(b []byte) error {
if p.MainConn == nil {
return nil
}
psocket, ok := p.MainConn.Context().(*ClientData)
if ok {
return psocket.SendPack(b)
2026-02-07 18:21:52 +08:00
} else {
return fmt.Errorf("链接错误,取消发包")
}
2026-02-10 22:09:15 +08:00
}
// 添加物品 返回成功添加的物品
// ItemAddBatch 批量添加物品,普通道具先按 ItemAdd 的规则校验上限,再批量落库。
func (p *Player) ItemAddBatch(items []data.ItemInfo) []data.ItemInfo {
if len(items) == 0 {
return nil
}
sendErr := func(code errorcode.ErrorCode) {
t1 := common.NewTomeeHeader(2601, p.Info.UserID)
t1.Result = uint32(code)
p.SendPack(t1.Pack(nil))
}
var (
regularItems = make([]data.ItemInfo, 0, len(items))
regularIndexes = make([]int, 0, len(items))
itemIDs = make([]uint32, 0, len(items))
seenIDs = make(map[uint32]struct{}, len(items))
specialSuccess = make(map[int]bool, len(items))
)
for idx, item := range items {
if item.ItemCnt <= 0 {
sendErr(errorcode.ErrorCodes.ErrSystemError200007)
continue
}
switch item.ItemId {
case 1, 3, 5, 9:
if p.ItemAdd(item.ItemId, item.ItemCnt) {
specialSuccess[idx] = true
}
default:
regularItems = append(regularItems, item)
regularIndexes = append(regularIndexes, idx)
itemID := uint32(item.ItemId)
if _, ok := seenIDs[itemID]; ok {
continue
}
seenIDs[itemID] = struct{}{}
itemIDs = append(itemIDs, itemID)
}
}
if len(regularItems) == 0 {
result := make([]data.ItemInfo, 0, len(items))
for idx, item := range items {
if specialSuccess[idx] {
result = append(result, item)
}
}
return result
}
currentItems := p.Service.Item.CheakItemM(itemIDs...)
currentMap := make(map[uint32]int64, len(currentItems))
for _, item := range currentItems {
currentMap[item.ItemId] = item.ItemCnt
}
maxMap := dictrvice.NewDictInfoService().GetMaxMap(itemIDs...)
batchItems := make([]data.ItemInfo, 0, len(regularItems))
pendingMap := make(map[uint32]int64, len(itemIDs))
for _, item := range regularItems {
itemID := uint32(item.ItemId)
itemmax := maxMap[itemID]
if itemmax == 0 {
cool.Logger.Error(context.TODO(), "物品不存在", p.Info.UserID, item.ItemId)
sendErr(errorcode.ErrorCodes.ErrSystemError200007)
continue
}
if currentMap[itemID]+pendingMap[itemID]+item.ItemCnt > int64(itemmax) {
println(p.Info.UserID, "物品超过拥有最大限制", item.ItemId)
sendErr(errorcode.ErrorCodes.ErrTooManyOfItem)
continue
}
pendingMap[itemID] += item.ItemCnt
batchItems = append(batchItems, item)
}
addedRegularItems, err := p.Service.Item.AddItemsChecked(batchItems, currentMap)
if err != nil {
sendErr(errorcode.ErrorCodes.ErrSystemError200007)
return nil
}
regularSuccess := make(map[int]bool, len(addedRegularItems))
regularPos := 0
for idx, item := range regularItems {
if regularPos < len(addedRegularItems) &&
addedRegularItems[regularPos].ItemId == item.ItemId &&
addedRegularItems[regularPos].ItemCnt == item.ItemCnt {
regularSuccess[regularIndexes[idx]] = true
regularPos++
}
}
result := make([]data.ItemInfo, 0, len(items))
for idx, item := range items {
if specialSuccess[idx] || regularSuccess[idx] {
result = append(result, item)
}
}
return result
}
func (p *Player) ItemAdd(ItemId, ItemCnt int64) (result bool) {
if ItemCnt <= 0 {
t1 := common.NewTomeeHeader(2601, p.Info.UserID)
t1.Result = uint32(errorcode.ErrorCodes.ErrSystemError200007)
p.SendPack(t1.Pack(nil)) //准备包由各自发,因为协议不一样
}
switch ItemId {
case 1: //塞尔豆
p.Info.Coins = p.Info.Coins + ItemCnt
return true
case 3: //累计经验
p.Info.ExpPool = p.Info.ExpPool + ItemCnt
return true
case 5: //金豆ItemAdd
p.User.UpdateGold(p.Info.UserID, int64(ItemCnt*100))
return true
case 9: //学习力
p.Info.EVPool = p.Info.EVPool + ItemCnt
default:
2026-01-21 20:46:05 +00:00
itemmax := dictrvice.NewDictInfoService().GetMax(ItemId)
if itemmax == 0 {
cool.Logger.Error(context.TODO(), "物品不存在", p.Info.UserID, ItemId)
t1 := common.NewTomeeHeader(2601, p.Info.UserID)
t1.Result = uint32(errorcode.ErrorCodes.ErrSystemError200007)
p.SendPack(t1.Pack(nil)) //准备包由各自发,因为协议不一样
return false
}
if p.Service.Item.CheakItem(uint32(ItemId))+int64(ItemCnt) > int64(itemmax) {
println(p.Info.UserID, "物品超过拥有最大限制", ItemId)
t1 := common.NewTomeeHeader(2601, p.Info.UserID)
t1.Result = uint32(errorcode.ErrorCodes.ErrTooManyOfItem)
p.SendPack(t1.Pack(nil)) //准备包由各自发,因为协议不一样
return false
}
if err := p.Service.Item.UPDATE(uint32(ItemId), gconv.Int(ItemCnt)); err != nil {
cool.Logger.Error(context.TODO(), "item add update failed", p.Info.UserID, ItemId, ItemCnt, err)
t1 := common.NewTomeeHeader(2601, p.Info.UserID)
t1.Result = uint32(errorcode.ErrorCodes.ErrSystemError200007)
p.SendPack(t1.Pack(nil)) //准备包由各自发,因为协议不一样
return false
}
return true
}
return false
}
2026-02-07 00:18:14 +08:00
// Kick 是否热更退出
func (player1 *Player) Kick(isquit bool) {
2026-02-02 23:47:06 +08:00
if player1.Info == nil {
return
}
2026-02-03 23:08:06 +08:00
head := common.NewTomeeHeader(1001, player1.Info.UserID)
2026-02-07 00:18:14 +08:00
head.Result = uint32(errorcode.ErrorCodes.ErrAccountLoggedInElsewhere)
if isquit {
head.Result = uint32(errorcode.ErrorCodes.ErrXinPlanSleepMode)
}
// 实际上这里有个问题,会造成重复保存问题
player1.SendPack(head.Pack(nil))
2026-02-03 22:44:13 +08:00
CloseChan := make(chan struct{})
player1.MainConn.CloseWithCallback(func(c gnet.Conn, err error) error {
close(CloseChan)
return nil
})
// --- 新增超时机制核心代码 ---
// 设定超时时间可根据业务需求调整这里以3秒为例
const kickTimeout = 10 * time.Second
timeout := false
select {
case <-CloseChan:
// 正常流程连接关闭回调已执行CloseChan 被关闭
case <-time.After(kickTimeout):
timeout = true
}
player1.SaveOnDisconnect()
if timeout {
service.NewBaseSysLogService().RecordKick(uint32(player1.Info.UserID), fmt.Sprintf("踢人操作超时(超时时间:%v", kickTimeout))
}
}
2026-02-03 23:08:06 +08:00
// 1是延迟踢人,0是强制踢人
func (player1 *Player) KickMessage() {
if player1.Info == nil {
return
}
// 取成功,否则创建
// player1.Save() //先保存数据再返回
head := common.NewTomeeHeader(1001, player1.Info.UserID)
head.Result = uint32(errorcode.ErrorCodes.ErrXinPlanSleepMode)
// 实际上这里有个问题,会造成重复保存问题
player1.SendPack(head.Pack(nil))
}
func (p *Player) Cheak(b error) {
if b != nil {
g.Log().Error(context.Background(), "出现错误", p.Info.UserID, b.Error())
}
}
func (p *Player) GiveTitle(id uint32) {
r := p.Service.Title.Can(id) //已经有了就不给了
2026-02-26 16:58:25 +08:00
if r {
return
}
p.Service.Title.Give(id)
p.SendPackCmd(50005, &info.S2C_50005{
Title: id,
})
}