feat(broadcast): 添加全服广播功能并完善相关逻辑

新增 Broadcast 结构体及 Server 的 Broadcast 方法,用于实现全服广播消息,
并在 RPC 客户端中增加对应接口。同时在 fight 模块中添加聊天信息结构体和处理逻辑。

refactor(pet_skill): 优化宠物技能设置逻辑

修复宠物技能替换判断条件错误的问题,并调整相关逻辑顺序以提高代码可读性与健壮性。

feat(chat): 实现战斗内聊天功能

新增战斗中的聊天指令结构体 ChatInfo 和对应的控制器方法 FightChat,
支持玩家在战斗中发送聊天消息。

refactor(item_buy): 调整金币购买道具的扣费方式

将原直接比较金币数量改为调用
This commit is contained in:
2025-11-25 16:36:55 +08:00
parent 40d72790ff
commit 3e1887c7b8
17 changed files with 115 additions and 30 deletions

View File

@@ -2,6 +2,7 @@ package xmlres
import (
"blazing/common/utils"
"encoding/json"
"os"
@@ -55,7 +56,7 @@ var (
GoldProductMap = make(map[int]GoldProductItem, 0)
)
func initfile() {
func Initfile() {
path1, _ := os.Getwd()
path = path1 + "/public/config/"
MapConfig = getXml[Maps](path + "210.xml")
@@ -133,8 +134,6 @@ func initfile() {
func init() {
initfile() //先初始化一次
go func() {
if !gfile.Exists(path) {

View File

@@ -36,6 +36,7 @@ func GetServerInfoList() []ServerInfo {
if ok {
cool.Loger.Info(context.TODO(), "服务器假踢人")
err := t.KickPerson(0) //实现指定服务器踢人
if err == nil {
// tt.Friends = v.Friends
ret1 = append(ret1, *tt)

View File

@@ -43,6 +43,7 @@ func getClient(id uint16) (*ClientHandler, bool) {
type ClientHandler struct {
KickPerson func(uint32) error //踢人,这里是返回具体的logic
QuitSelf func(int) error //关闭服务器进程
Broadcast func(string) int //全服广播,返回的是在线人数
}
// Define the server handler

View File

@@ -7,6 +7,23 @@ import (
"fmt"
)
type Broadcast struct {
Name string
}
func (h *Server) Broadcast(t string) int {
cool.Loger.Info(context.TODO(), "全服广播", t)
var count int
player.Mainplayer.Range(func(key uint32, value *player.Player) bool {
count++
value.SendPackCmd(50003, &Broadcast{
Name: t,
})
return true
})
return count
}
func (h *Server) KickPerson(a int) error {
cool.Loger.Info(context.TODO(), "检测到踢人请求", a)
if a == 0 {

View File

@@ -17,3 +17,10 @@ func LastFourElements[T any](s []T, n1 int) []T {
// 切片长度大于4时返回最后4个元素从n-4索引到末尾
return s[n-n1:]
}
func RemoveLast(s string) string {
if s == "" {
return ""
}
runes := []rune(s)
return string(runes[:len(runes)-1])
}

View File

@@ -84,3 +84,12 @@ func (h Controller) UsePetItemInboundInfo(data *fight.UsePetItemInboundInfo, c *
return nil, -1
}
func (h Controller) FightChat(data *fight.ChatInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if c.FightC == nil {
return nil, errorcode.ErrorCodes.ErrBattleEnded
}
c.FightC.Chat(c, data.Message)
return nil, -1
}

View File

@@ -58,7 +58,7 @@ func (h Controller) BuyMItem(data *item.BuyMultiInboundInfo, c *player.Player) (
func (h Controller) BuyGoldItem(data *item.C2S_GOLD_BUY_PRODUCT, c *player.Player) (result *item.S2C_GoldBuyProductInfo, err errorcode.ErrorCode) {
r := xmlres.GoldProductMap[int(data.ProductID)]
if uint32(data.Count)*uint32(gconv.Uint32(r.Price)) > c.Info.GoldBean {
if !c.UseGold(uint32(data.Count) * uint32(gconv.Uint32(r.Price))) {
return nil, errorcode.ErrorCodes.ErrSystemError
}
c.ItemAdd(model.ItemInfo{ItemId: uint32(gconv.Uint32(r.ItemID)), ItemCnt: uint32(data.Count)})
@@ -69,6 +69,7 @@ func (h Controller) BuyGoldItem(data *item.C2S_GOLD_BUY_PRODUCT, c *player.Playe
PayGold: uint32(data.Count) * uint32(gconv.Uint32(r.Price)),
Reserved: 0,
}
return
}

View File

@@ -16,17 +16,16 @@ func (h Controller) SetPetSkill(data *pet.ChangeSkillInfo, c *player.Player) (re
}
_, onpet, ok := c.FindPet(data.CatchTime)
if ok {
_, HasSkill, ok := utils.FindWithIndex(onpet.SkillList, func(item model.SkillInfo) bool { //已经存在技能
return item.ID == data.ReplaceSkill
})
if !ok {
HasSkill.ID = data.ReplaceSkill
HasSkill.PP = uint32(xmlres.SkillMap[int(HasSkill.ID)].MaxPP)
}
if !ok {
return result, errorcode.ErrorCodes.ErrSystemBusy
}
_, HasSkill, ok := utils.FindWithIndex(onpet.SkillList, func(item model.SkillInfo) bool { //已经存在技能
return item.ID == data.HasSkill
})
if !ok {
HasSkill.ID = data.ReplaceSkill
HasSkill.PP = uint32(xmlres.SkillMap[int(HasSkill.ID)].MaxPP)
}
return &pet.ChangeSkillOutInfo{
CatchTime: data.CatchTime,
}, 0

View File

@@ -2,6 +2,7 @@ package controller
import (
"blazing/common/socket/errorcode"
"blazing/common/utils"
"blazing/cool"
"blazing/logic/service/item"
"blazing/logic/service/player"
@@ -51,7 +52,7 @@ func (h Controller) Chat(data *user.ChatInboundInfo, c *player.Player) (result *
result = &user.ChatOutboundInfo{
Message: string([]byte(data.Message)[:data.MessageLen-1]),
Message: utils.RemoveLast(data.Message),
SenderNickname: c.Info.Nick,
SenderId: c.Info.UserID,
}

View File

@@ -1,6 +1,7 @@
package main
import (
"blazing/common/data/xmlres"
"blazing/common/rpc"
"blazing/common/socket"
@@ -76,7 +77,7 @@ func Start(serverid uint16) {
controller.Maincontroller.RPCClient = *t //将RPC赋值Start
controller.Maincontroller.Port = uint16(port) //赋值服务器ID
xmlres.Initfile()
blservice.NewLoginServiceService().SetServerID(serverid, gconv.Uint16(port))
ser.Boot()

View File

@@ -17,7 +17,7 @@ type FightI interface {
GetRand() *rand.Rand
LoadPercent(c PlayerI, percent int32)
UseItem(c PlayerI, cacthid, itemid uint32)
Chat(c PlayerI, msg string)
IsFirst(c PlayerI) bool
GetOverChan() chan struct{}
}

View File

@@ -117,3 +117,9 @@ type UsePetItemInboundInfo struct {
ItemId uint32 `description:"使用的物品ID" codec:"itemId"` // 结构体标签模拟@FieldDescription和@AutoCodec注解
Reversed1 uint32 `description:"填充字段 0" codec:"reversed1"` // reversed1对应原Java的填充字段
}
type ChatInfo struct {
Head common.TomeeHeader `cmd:"50002" struc:"skip"`
Reserve uint32 `json:"reserve" fieldDescription:"填充 默认值为0" uint:"true"` // @UInt long reserve无符号长整数
MessageLen uint32 `struc:"sizeof=Message"`
Message string `json:"message" fieldDescription:"消息内容, 结束符为utf-8的数字0"` // 消息内容包含utf-8空字符('\x00')作为结束符
}

View File

@@ -1,15 +1,18 @@
package fight
import (
"blazing/common/data/xmlres"
"blazing/common/socket/errorcode"
"blazing/common/utils"
"blazing/cool"
"blazing/modules/blazing/model"
"fmt"
"blazing/logic/service/common"
"blazing/logic/service/fight/action"
"blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/modules/blazing/model"
"blazing/logic/service/user"
"math/rand"
"sync"
"time"
@@ -94,10 +97,23 @@ func (f *FightC) IsFirst(play common.PlayerI) bool {
return f.First.Player == play
}
func (f *FightC) Chat(c common.PlayerI, msg string) {
f.GetInputByPlayer(c, true).Player.SendPackCmd(50002, &user.ChatOutboundInfo{
SenderId: c.GetInfo().UserID,
SenderNickname: c.GetInfo().Nick,
Message: utils.RemoveLast(msg),
})
}
// 加载进度
func (f *FightC) LoadPercent(c common.PlayerI, percent int32) {
if f.Info.Mode == info.BattleMode.PET_MELEE {
return
}
if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC {
return
}
f.GetInputByPlayer(c, true).Player.SendPackCmd(2441, &info.LoadPercentOutboundInfo{
Id: c.GetInfo().UserID,
Percent: uint32(percent),
@@ -105,6 +121,18 @@ func (f *FightC) LoadPercent(c common.PlayerI, percent int32) {
}
var meetpet = make(map[int]model.PetInfo)
func initmeetpet() {
meetpet = make(map[int]model.PetInfo)
for i, v := range xmlres.PetMAP {
if v.EvolvesTo == 0 && v.ID < 2000 {
r := model.GenPetInfo(int(v.ID), 24, -1, -1, -1, 100)
meetpet[i] = *r
}
}
}
func (f *FightC) initplayer(c common.PlayerI) (*input.Input, errorcode.ErrorCode) {
if !c.CanFight() {
@@ -126,18 +154,18 @@ func (f *FightC) initplayer(c common.PlayerI) (*input.Input, errorcode.ErrorCode
in.AllPet = in.AllPet[:1]
case info.BattleMode.PET_MELEE:
in.AllPet = make([]*info.BattlePetEntity, 0)
for _, v := range RandomElfIDs(3) {
p := model.GenPetInfo(v, 24, -1, -1, -1, 100)
//p.CatchTime = uint32(v)
p.Update()
p.Update_EXP()
p = model.GenPetInfo(int(p.ID), 24, -1, -1, -1, 100)
//p.CalculatePetPane()
p.CatchTime = uint32(v)
in.AllPet = append(in.AllPet, info.CreateBattlePetEntity(*p, f.rand))
if len(meetpet) == 0 {
initmeetpet()
}
for i, v := range meetpet {
if len(in.AllPet) > 2 {
break
}
v.CatchTime = uint32(i)
in.AllPet = append(in.AllPet, info.CreateBattlePetEntity(v, f.rand))
}
//in.AllPet = in.AllPet[:3]
default:
}

View File

@@ -18,11 +18,14 @@ func (p *Player) AddPetExp(petinfo *model.PetInfo, addExp uint32) {
addExp = utils.Min(addExp, p.Info.ExpPool)
originalLevel := petinfo.Level
Exp := petinfo.Exp + addExp
petinfo.Update_EXP()
petinfo.Update()
p.Info.ExpPool -= addExp //减去已使用的经验
for Exp >= petinfo.NextLvExp {
petinfo.Level++
petinfo.Update_EXP()
petinfo.Update()
Exp -= petinfo.LvExp
if originalLevel < 100 && petinfo.Level == 100 { //升到100了
p.Info.ExpPool += Exp //减去已使用的经验

View File

@@ -86,6 +86,15 @@ func (p *Player) UseCoins(t uint32) bool {
p.Info.Coins = p.Info.Coins - t
return true
}
func (p *Player) UseGold(t uint32) bool {
if p.Info.GoldBean < t {
return false
}
p.Info.GoldBean = p.Info.GoldBean - t
return true
}
func (p *Player) GetAction() {

View File

@@ -35,6 +35,7 @@ require (
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/samber/lo v1.52.0
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/sdk v1.24.0 // indirect

View File

@@ -62,6 +62,8 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tnnmigga/enum v1.0.2 h1:Yvchx0Esc01X5HiphW78sKzH/RXKttdFsfPO1ARiOa4=