Files
bl/logic/service/player/player.go
昔念 5f47bf0589
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
1
2026-04-15 03:22:59 +08:00

524 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package player
import (
"blazing/common/data"
"blazing/common/data/xmlres"
"blazing/common/socket/errorcode"
"blazing/cool"
"blazing/logic/service/common"
"blazing/logic/service/fight/info"
"blazing/logic/service/space"
"fmt"
"sync"
"sync/atomic"
"time"
"blazing/modules/base/service"
config "blazing/modules/config/service"
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"
csmap "github.com/mhmtszr/concurrent-swiss-map"
"github.com/panjf2000/gnet/v2"
)
// Mainplayer 全局玩家数据存储映射
var Mainplayer = csmap.New[uint32, *ClientData]()
type OgrePetInfo struct {
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"`
}
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 //是否登录
IsJoin bool //是否加入RPC对局 用重连函数写临时缓存join重连后统一join
Done
MapNPC *time.Timer
context.Context
Fightinfo info.Fightinfo // 当前邀请的玩家ID
Logintime uint32 // 当前登录时间
OgrePet
PetDisplay PetDisplayState
Service *blservice.UserService
User *service.BaseSysUserService
// PVP被邀请信息
HavePVPinfo []common.PlayerI
monsters [3]int
// 0 无,1可以刷怪,2是切换过地图
Canmon uint32 // 可以刷怪
CurDark uint32
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
}
func (p *Player) UseGold(amount int64) bool {
if p.User.GetGold(uint(p.Info.UserID)) < amount {
return false
}
return true
}
func (p *Player) GetAction() {
}
// InvitePlayer 邀请玩家进行对战
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 获取玩家的战斗信息
func (p *Player) Getfightinfo() info.Fightinfo {
return p.Fightinfo
}
// QuitFight 退出战斗
func (p *Player) QuitFight() {
p.FightC = nil
atomic.StoreUint32(&p.Fightinfo.Mode, 0)
}
// GetSpace 获取玩家所在的空间
func (p *Player) GetSpace() *space.Space {
return space.GetSpace(p.Info.MapID)
}
func (p *Player) CurrentMapPetLevelLimit() uint32 {
if p == nil {
return 100
}
currentSpace := p.GetSpace()
if currentSpace != nil && currentSpace.IsLevelBreakMap {
return 0
}
return 100
}
func (p *Player) IsCurrentMapLevelBreak() bool {
return p != nil && p.CurrentMapPetLevelLimit() == 0
}
// CanFight 检查玩家是否可以进行战斗
// 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
}
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
}
}
// 遍历完所有宠物都没有血量大于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)
} else {
return fmt.Errorf("链接错误,取消发包")
}
}
// 添加物品 返回成功添加的物品
// 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:
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
}
// Kick 是否热更退出
func (player1 *Player) Kick(isquit bool) {
if player1.Info == nil {
return
}
head := common.NewTomeeHeader(1001, player1.Info.UserID)
head.Result = uint32(errorcode.ErrorCodes.ErrAccountLoggedInElsewhere)
if isquit {
head.Result = uint32(errorcode.ErrorCodes.ErrXinPlanSleepMode)
}
// 实际上这里有个问题,会造成重复保存问题
player1.SendPack(head.Pack(nil))
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))
}
}
// 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) //已经有了就不给了
if r {
return
}
p.Service.Title.Give(id)
p.SendPackCmd(50005, &info.S2C_50005{
Title: id,
})
}