524 lines
13 KiB
Go
524 lines
13 KiB
Go
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无战斗,1PVP,2,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,
|
||
})
|
||
}
|