Files
bl/logic/service/player/player.go
昔念 35c89215f7 ```
feat(player): 重构玩家金币系统,使用BaseSysUserService管理金币

将玩家金币逻辑从PlayerInfo中移除,改为通过BaseSysUserService进行统一管理。
新增了金币的获取与设置方法,支持以分为单位的精确计算。
调整了登录时用户服务的初始化逻辑,确保User字段正确赋值。

fix(pet): 修复宠物性格道具使用逻辑错误

更新了多个性格相关道具的处理方式,包括新增的性格转换道具范围。
修正了性格随机与指定逻辑,避免越界问题并增强可维护性。

feat(fight): 战斗初始化时恢复宠物状态

在战斗初始化阶段调用宠物治愈方法,确保战斗开始前宠物处于健康状态。

feat(admin): 调整管理员会话获取接口参数类型

修改GetPerson方法传入参数为uint32类型,提高数据一致性与安全性。

refactor(model): 移除PlayerInfo中的GoldBean字段

金币字段不再存储于PlayerInfo结构体中,转而由BaseSysUser模块统一管理。
```
2025-12-06 23:59:00 +08:00

365 lines
7.9 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/xmlres"
"blazing/common/socket/errorcode"
"blazing/common/utils"
"blazing/cool"
"blazing/logic/service/common"
"blazing/logic/service/fight/info"
"blazing/logic/service/space"
"math/rand"
"strings"
"sync/atomic"
"blazing/modules/base/service"
"blazing/modules/blazing/model"
blservice "blazing/modules/blazing/service"
"context"
"time"
"github.com/antlabs/timer"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
"github.com/panjf2000/gnet/v2"
)
func ConutPlayer() int {
count := 0
Mainplayer.Range(func(uint32, *Player) bool {
count++
return true // 继续遍历
})
return count
}
var Mainplayer = &utils.SyncMap[uint32, *Player]{} //玩家数据
type OgreInfo struct {
Data [9]OgrePetInfo
}
type OgrePetInfo struct {
Id uint32
Shiny uint32
Lv uint32 `struc:"skip"` //等级
Item uint32 `struc:"skip"` //奖励,如果有的话
}
type Player struct {
MainConn gnet.Conn
baseplayer
IsLogin bool //是否登录
Done
StopChan timer.TimeNoder
context.Context
Fightinfo info.Fightinfo //当前邀请的玩家ID
Logintime uint32 //当前登录时间
OgreInfo OgreInfo
Service *blservice.UserService
User *service.BaseSysUserService
// PVP被邀请信息
HavePVPinfo []common.PlayerI
monsters [3]int
//0 无,1可以刷怪,2是切换过地图
Canmon uint32 //可以刷怪
// Changemap bool //是否切换过地图
}
// PlayerOption 定义配置 Player 的函数类型
type PlayerOption func(*Player)
func WithConn(c gnet.Conn) PlayerOption {
return func(p *Player) {
p.MainConn = c
}
}
func (p *Player) UseCoins(t uint32) bool {
if p.Info.Coins < t {
return false
}
p.Info.Coins = p.Info.Coins - t
return true
}
func (p *Player) UseGold(t uint32) bool {
if p.User.GetGold(uint(p.Info.UserID)) < t {
return false
}
//p.Info.GoldBean = p.Info.GoldBean - t
return true
}
func (p *Player) GetAction() {
}
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,
}))
}
func (p *Player) Getfightinfo() info.Fightinfo {
return p.Fightinfo
}
func (p *Player) QuitFight() {
p.FightC = nil
atomic.StoreUint32(&p.Fightinfo.Mode, 0)
}
func (p *Player) GetSpace() *space.Space {
return space.GetSpace(p.Info.MapID)
}
// 0无战斗1PVP2,BOOS,3PVE
func (p *Player) CanFight() bool {
if len(p.Info.PetList) == 0 {
atomic.StoreUint32(&p.Fightinfo.Mode, 0)
return false
}
if p.FightC != nil {
atomic.StoreUint32(&p.Fightinfo.Mode, 0)
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的才不能战斗
atomic.StoreUint32(&p.Fightinfo.Mode, 0)
return false
// }
// return false
}
// 刷怪具体实现
func (p *Player) SpawnMonsters() {
// 获取当前地图的怪物配置
// 创建数据包
tt := common.NewTomeeHeader(2004, p.Info.UserID)
p.genMonster(p.Info.MapID) //生成野怪
p.SendPack(tt.Pack(&p.OgreInfo))
}
func (p *Player) SendPack(b []byte) error {
if p.MainConn == nil {
return nil
}
_, ok := p.MainConn.Context().(*ClientData)
if ok {
return p.MainConn.Context().(*ClientData).SendPack(b)
}
return nil
}
// 2. 从 string 类型 slice 随机选一个元素
func RandomStringFromSlice(s []string) string {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
randomIdx := r.Intn(len(s))
return s[randomIdx]
}
// 应该根据怪物信息决定后端生成
func (p *Player) genMonster(mapid uint32) {
var oldnum, newNum int
p.monsters, oldnum, newNum = replaceOneNumber(p.monsters)
// 设置怪物信息
t1 := OgreInfo{}
mapss, ok := xmlres.MonsterMap[gconv.Int(mapid)]
if ok && mapss.Monsters != nil {
ok, _, _ := p.PlayerCaptureContext.Roll(mapss.Monsters.WildBonusProb, mapss.Monsters.WildBonusTotalProb)
for i, m := range mapss.Monsters.Monsters { //这里是9个
id := strings.Split(m.ID, " ")
lv := strings.Split(m.Lv, " ")
ttt := OgrePetInfo{
Id: gconv.Uint32(RandomStringFromSlice(id)),
}
if ok {
ttt.Item = uint32(mapss.Monsters.ItemBonusID)
}
if ttt.Id != 0 {
ttt.Shiny = 0 //待确认是否刷新异色
ttt.Lv = gconv.Uint32(RandomStringFromSlice(lv))
}
t1.Data[i] = ttt
}
}
if atomic.CompareAndSwapUint32(&p.Canmon, 2, 1) {
p.OgreInfo = OgreInfo{} //切地图清空
for i := 0; i < 3; i++ {
p.OgreInfo.Data[p.monsters[i]] = t1.Data[p.monsters[i]]
}
}
p.OgreInfo.Data[oldnum] = OgrePetInfo{}
p.OgreInfo.Data[newNum] = t1.Data[newNum]
}
// 生成0-9之间三个不重复的随机数 进地图5s
func generateThreeUniqueNumbers() [3]int {
selected := make(map[int]bool)
var result [3]int
index := 0
for index < 3 {
num := grand.Intn(9)
if !selected[num] {
selected[num] = true
result[index] = num
index++
}
}
return result
}
// 从三个数字中移除一个并从剩余6个数字中选一个补充 10s
func replaceOneNumber(original [3]int) ([3]int, int, int) {
// 随机选择要移除的索引0-2
removeIndex := grand.Intn(3)
removedNum := original[removeIndex]
// 找出所有不在原始数组中的数字(候选数字)
candidates := []int{}
originalMap := make(map[int]bool)
for _, num := range original {
originalMap[num] = true
}
for i := 0; i < 8; i++ {
if !originalMap[i] {
candidates = append(candidates, i)
}
}
// 从候选数字中随机选择一个
newNum := candidates[grand.Intn(len(candidates))]
// 创建新数组并替换数字
newNumbers := original
newNumbers[removeIndex] = newNum
return newNumbers, removedNum, newNum
}
// 添加物品 返回成功添加的物品
func (p *Player) ItemAdd(t ...model.ItemInfo) (result []model.ItemInfo) {
var ttt []model.ItemInfo
for _, v := range t {
switch v.ItemId {
case 1: //塞尔豆
p.Info.Coins = p.Info.Coins + v.ItemCnt
case 3: //累计经验
p.Info.ExpPool = p.Info.ExpPool + v.ItemCnt
case 5: //金豆ItemAdd
p.User.SetGold(p.Info.UserID, uint32(p.User.GetGold(uint(p.Info.UserID))+v.ItemCnt*100))
default:
ttt = append(ttt, v)
}
}
for _, v := range ttt {
itemx, ok := xmlres.ItemsMAP[int(v.ItemId)]
if !ok {
cool.Loger.Error(context.TODO(), "物品不存在", v.ItemId)
t1 := common.NewTomeeHeader(2601, p.Info.UserID)
t1.Result = uint32(errorcode.ErrorCodes.ErrSystemError200007)
p.SendPack(t1.Pack(nil)) //准备包由各自发,因为协议不一样
continue
}
if itemx.Max == 0 {
itemx.Max = 1
}
if p.Service.Item.CheakItem(v.ItemId)+v.ItemCnt > uint32(itemx.Max) {
cool.Loger.Error(context.TODO(), "物品超过拥有最大限制", v.ItemId)
t1 := common.NewTomeeHeader(2601, p.Info.UserID)
t1.Result = uint32(errorcode.ErrorCodes.ErrTooManyOfItem)
p.SendPack(t1.Pack(nil)) //准备包由各自发,因为协议不一样
continue
}
p.Service.Item.AddItem(v.ItemId, v.ItemCnt)
result = append(result, v)
}
return
}
func (player1 *Player) Kick() {
if player1.IsLogin {
//取成功,否则创建
//player1.Save() //先保存数据再返回
head := common.NewTomeeHeader(1001, player1.Info.UserID)
head.Result = uint32(errorcode.ErrorCodes.ErrAccountLoggedInElsewhere)
//实际上这里有个问题,会造成重复保存问题
player1.SendPack(head.Pack(nil))
CloseChan := make(chan struct{})
player1.MainConn.CloseWithCallback(func(c gnet.Conn, err error) error {
close(CloseChan)
return nil
})
<-CloseChan
}
}
func (p *Player) Cheak(b error) {
if b != nil {
g.Log().Error(context.Background(), "出现错误", p.Info.UserID, b.Error())
}
}