Merge branch 'main' of https://cnb.cool/blzing/blazing
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"blazing/modules/player/model"
|
||||
)
|
||||
|
||||
// FightI 定义 common 服务层依赖的战斗操作接口。
|
||||
type FightI interface {
|
||||
Over(c PlayerI, id model.EnumBattleOverReason) //逃跑
|
||||
UseSkill(c PlayerI, id uint32) //使用技能
|
||||
|
||||
@@ -7,24 +7,25 @@ import (
|
||||
"github.com/gogf/gf/v2/os/glog"
|
||||
)
|
||||
|
||||
// MyWriter 自定义日志写入器,用于逻辑服日志转发。
|
||||
type MyWriter struct {
|
||||
logger *glog.Logger
|
||||
user uint32
|
||||
logger *glog.Logger // 底层 glog 实例。
|
||||
user uint32 // 关联的玩家 ID。
|
||||
}
|
||||
|
||||
// Write 实现 io.Writer,并将日志写入系统日志与底层 logger。
|
||||
func (w *MyWriter) Write(p []byte) (n int, err error) {
|
||||
var (
|
||||
s = string(p)
|
||||
//ctx = context.Background()
|
||||
)
|
||||
|
||||
service.NewBaseSysLogService().RecordLog(w.user, s)
|
||||
return w.logger.Write(p)
|
||||
}
|
||||
|
||||
func init() {
|
||||
cool.Logger.SetWriter(&MyWriter{
|
||||
logger: glog.New(),
|
||||
})
|
||||
cool.Logger.SetAsync(true)
|
||||
|
||||
}
|
||||
|
||||
@@ -8,20 +8,18 @@ import (
|
||||
"github.com/lunixbochs/struc"
|
||||
)
|
||||
|
||||
// TomeeHeader 结构体字段定义
|
||||
// TomeeHeader 定义协议包头。
|
||||
type TomeeHeader struct {
|
||||
Len uint32 `json:"len"`
|
||||
Version byte `json:"version" struc:"[1]byte"`
|
||||
CMD uint32 `json:"cmdId" struc:"uint32"`
|
||||
UserID uint32 `json:"userId"`
|
||||
//Error uint32 `json:"error" struc:"skip"`
|
||||
|
||||
Result uint32 `json:"result"`
|
||||
Data []byte `json:"data" struc:"skip"` //组包忽略此字段// struc:"skip"
|
||||
Res []byte `struc:"skip"`
|
||||
//Return []byte `struc:"skip"` //返回记录
|
||||
Len uint32 `json:"len"` // 包总长度(包头 + 数据体)。
|
||||
Version byte `json:"version" struc:"[1]byte"` // 协议版本。
|
||||
CMD uint32 `json:"cmdId" struc:"uint32"` // 命令 ID。
|
||||
UserID uint32 `json:"userId"` // 玩家 ID。
|
||||
Result uint32 `json:"result"` // 结果码。
|
||||
Data []byte `json:"data" struc:"skip"` // 数据体,序列化时跳过。
|
||||
Res []byte `struc:"skip"` // 预留返回数据,序列化时跳过。
|
||||
}
|
||||
|
||||
// NewTomeeHeader 创建用于下行封包的默认 TomeeHeader。
|
||||
func NewTomeeHeader(cmd uint32, userid uint32) *TomeeHeader {
|
||||
|
||||
return &TomeeHeader{
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"blazing/modules/player/model"
|
||||
)
|
||||
|
||||
// PlayerI 定义战斗与 common 服务依赖的最小玩家能力接口。
|
||||
type PlayerI interface {
|
||||
ApplyPetDisplayInfo(*space.SimpleInfo)
|
||||
GetPlayerCaptureContext() *info.PlayerCaptureContext
|
||||
|
||||
99
logic/service/fight/boss/NewSeIdx_247.go
Normal file
99
logic/service/fight/boss/NewSeIdx_247.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package effect
|
||||
|
||||
import (
|
||||
"blazing/logic/service/fight/action"
|
||||
"blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/fight/input"
|
||||
)
|
||||
|
||||
// 247. 固定增加体力/攻击/防御/特攻/特防/速度;(a1-a6: hp/atk/def/spatk/spdef/spd)
|
||||
type NewSel247 struct {
|
||||
NewSel0
|
||||
}
|
||||
|
||||
func (e *NewSel247) TurnStart(fattack *action.SelectSkillAction, sattack *action.SelectSkillAction) {
|
||||
if !e.IsOwner() {
|
||||
return
|
||||
}
|
||||
|
||||
pet := e.Ctx().Our.CurPet[0]
|
||||
if pet == nil {
|
||||
return
|
||||
}
|
||||
|
||||
hpBonus := uint32(e.Args()[0].IntPart())
|
||||
if hpBonus > 0 {
|
||||
pet.Info.MaxHp += hpBonus
|
||||
pet.Info.Hp += hpBonus
|
||||
}
|
||||
|
||||
for i, propIdx := range []int{0, 1, 2, 3, 4} {
|
||||
add := uint32(e.Args()[i+1].IntPart())
|
||||
if add == 0 {
|
||||
continue
|
||||
}
|
||||
pet.Info.Prop[propIdx] += add
|
||||
}
|
||||
}
|
||||
|
||||
func (e *NewSel247) TurnEnd() {
|
||||
if !e.IsOwner() {
|
||||
return
|
||||
}
|
||||
|
||||
pet := e.Ctx().Our.CurPet[0]
|
||||
if pet == nil {
|
||||
return
|
||||
}
|
||||
|
||||
hpBonus := uint32(e.Args()[0].IntPart())
|
||||
if hpBonus > 0 {
|
||||
if pet.Info.MaxHp > hpBonus {
|
||||
pet.Info.MaxHp -= hpBonus
|
||||
} else {
|
||||
pet.Info.MaxHp = 1
|
||||
}
|
||||
if pet.Info.Hp > pet.Info.MaxHp {
|
||||
pet.Info.Hp = pet.Info.MaxHp
|
||||
}
|
||||
}
|
||||
|
||||
for i, propIdx := range []int{0, 1, 2, 3, 4} {
|
||||
sub := uint32(e.Args()[i+1].IntPart())
|
||||
if sub == 0 {
|
||||
continue
|
||||
}
|
||||
if pet.Info.Prop[propIdx] > sub {
|
||||
pet.Info.Prop[propIdx] -= sub
|
||||
} else {
|
||||
pet.Info.Prop[propIdx] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type NewSel239 struct {
|
||||
NewSel0
|
||||
}
|
||||
|
||||
func (e *NewSel239) ActionStart(a, b *action.SelectSkillAction) bool {
|
||||
if !e.IsOwner() {
|
||||
return true
|
||||
}
|
||||
if e.Ctx().SkillEntity == nil {
|
||||
return true
|
||||
}
|
||||
if e.Ctx().SkillEntity.Category() == info.Category.STATUS {
|
||||
return true
|
||||
}
|
||||
if len(e.Args()) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
e.Ctx().SkillEntity.XML.Power += int(e.Args()[0].IntPart())
|
||||
return true
|
||||
}
|
||||
|
||||
func init() {
|
||||
input.InitEffect(input.EffectType.NewSel, 239, &NewSel239{})
|
||||
input.InitEffect(input.EffectType.NewSel, 247, &NewSel247{})
|
||||
}
|
||||
@@ -11,10 +11,9 @@ type NewSel26 struct {
|
||||
}
|
||||
|
||||
func (e *NewSel26) TurnStart(fattack *action.SelectSkillAction, sattack *action.SelectSkillAction) {
|
||||
e.Ctx().Our.CurPet[0].Info.Prop[int(e.Args()[0].IntPart())] += uint32(e.Args()[1].IntPart())
|
||||
}
|
||||
|
||||
func (e *NewSel26) TurnEnd() {
|
||||
e.Ctx().Our.CurPet[0].Info.Prop[int(e.Args()[0].IntPart())] -= uint32(e.Args()[1].IntPart())
|
||||
}
|
||||
func init() {
|
||||
input.InitEffect(input.EffectType.NewSel, 26, &NewSel26{})
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
package effect
|
||||
|
||||
import (
|
||||
"blazing/logic/service/fight/input"
|
||||
)
|
||||
import "blazing/logic/service/fight/input"
|
||||
|
||||
// 501. g1. 最后一个死 (只要有队友没死, 则自己又恢复hp和pp)
|
||||
// TODO: 需要了解如何判断队友状态并恢复HP和PP
|
||||
type NewSel501 struct {
|
||||
NewSel0
|
||||
}
|
||||
|
||||
// SwitchOut 在拥有者死亡离场时触发;若仍有存活队友,则把自己回满留作后续再上场。
|
||||
func (e *NewSel501) SwitchOut(in *input.Input) bool {
|
||||
//魂印特性有不在场的情况,绑定时候将精灵和特性绑定
|
||||
if e.ID().GetCatchTime() != e.Ctx().Our.CurPet[0].Info.CatchTime {
|
||||
owner := e.SourceInput()
|
||||
if owner == nil || in != owner || !e.IsOwner() {
|
||||
return true
|
||||
}
|
||||
currentPet := owner.CurrentPet()
|
||||
if currentPet == nil || currentPet.Info.Hp > 0 || !owner.HasLivingTeammate() {
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO: 检查是否有队友还活着
|
||||
// 如果有队友活着,恢复自身HP和PP
|
||||
// e.Ctx().Our.Heal(e.Ctx().Our, nil, e.Ctx().Our.CurPet[0].GetMaxHP())
|
||||
// TODO: 恢复PP值的方法
|
||||
|
||||
currentPet.Info.Hp = currentPet.Info.MaxHp
|
||||
owner.HealPP(-1)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
package effect
|
||||
|
||||
import (
|
||||
"blazing/logic/service/fight/input"
|
||||
)
|
||||
import "blazing/logic/service/fight/input"
|
||||
|
||||
// 502. g2. 如果自身死亡, 恢复队友所有体力和PP值
|
||||
// TODO: 实现恢复队友所有体力和PP值的核心逻辑
|
||||
type NewSel502 struct {
|
||||
NewSel0
|
||||
}
|
||||
|
||||
// TODO: 需要找到精灵死亡时的回调接口
|
||||
// SwitchOut 在拥有者死亡离场时触发;把仍在场的队友体力和 PP 恢复到满值。
|
||||
func (e *NewSel502) SwitchOut(in *input.Input) bool {
|
||||
//魂印特性有不在场的情况,绑定时候将精灵和特性绑定
|
||||
if e.ID().GetCatchTime() != e.Ctx().Our.CurPet[0].Info.CatchTime {
|
||||
owner := e.SourceInput()
|
||||
if owner == nil || in != owner || !e.IsOwner() {
|
||||
return true
|
||||
}
|
||||
currentPet := owner.CurrentPet()
|
||||
if currentPet == nil || currentPet.Info.Hp > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO: 检查精灵是否死亡(HP为0)
|
||||
// 如果死亡,恢复队友所有体力和PP值
|
||||
// 需要遍历队友的精灵并调用相应的方法
|
||||
for _, teammate := range owner.LivingTeammates() {
|
||||
pet := teammate.CurrentPet()
|
||||
if pet == nil {
|
||||
continue
|
||||
}
|
||||
pet.Info.Hp = pet.Info.MaxHp
|
||||
teammate.HealPP(-1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -6,20 +6,35 @@ import (
|
||||
)
|
||||
|
||||
// 503. g3. 群体攻击技能可额外增加一个目标(最多不超过5个目标)
|
||||
// TODO: 需要了解如何修改群体攻击技能的目标数量
|
||||
type NewSel503 struct {
|
||||
NewSel0
|
||||
}
|
||||
|
||||
// TurnStart 在拥有者本回合准备出手时触发;若本次技能是群体技能,则把目标数额外加 1。
|
||||
func (e *NewSel503) TurnStart(fattack *action.SelectSkillAction, sattack *action.SelectSkillAction) {
|
||||
//魂印特性有不在场的情况,绑定时候将精灵和特性绑定
|
||||
if e.ID().GetCatchTime() != e.Ctx().Our.CurPet[0].Info.CatchTime {
|
||||
owner := e.SourceInput()
|
||||
if owner == nil || !e.IsOwner() {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: 检查技能是否是群体攻击技能
|
||||
// 如果是群体攻击,增加一个目标(最多不超过5个)
|
||||
// 需要了解技能的目标数量限制机制
|
||||
for _, act := range []*action.SelectSkillAction{fattack, sattack} {
|
||||
if act == nil || act.SkillEntity == nil || act.SkillEntity.Pet == nil {
|
||||
continue
|
||||
}
|
||||
if act.SkillEntity.Pet.Info.CatchTime != e.ID().GetCatchTime() {
|
||||
continue
|
||||
}
|
||||
if act.SkillEntity.XML.AtkType != 0 {
|
||||
return
|
||||
}
|
||||
if act.SkillEntity.XML.AtkNum <= 0 {
|
||||
act.SkillEntity.XML.AtkNum = 1
|
||||
}
|
||||
if act.SkillEntity.XML.AtkNum < 5 {
|
||||
act.SkillEntity.XML.AtkNum++
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
148
logic/service/fight/cmd_unified.go
Normal file
148
logic/service/fight/cmd_unified.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package fight
|
||||
|
||||
import (
|
||||
"blazing/logic/service/common"
|
||||
"blazing/modules/player/model"
|
||||
)
|
||||
|
||||
// FightActionType 表示统一动作包里的动作类型。
|
||||
type FightActionType string
|
||||
|
||||
const (
|
||||
// FightActionTypeSkill 表示使用技能。
|
||||
FightActionTypeSkill FightActionType = "skill"
|
||||
// FightActionTypeItem 表示使用道具。
|
||||
FightActionTypeItem FightActionType = "item"
|
||||
// FightActionTypeChange 表示主动切宠。
|
||||
FightActionTypeChange FightActionType = "change"
|
||||
// FightActionTypeEscape 表示逃跑。
|
||||
FightActionTypeEscape FightActionType = "escape"
|
||||
// FightActionTypeChat 表示战斗内聊天。
|
||||
FightActionTypeChat FightActionType = "chat"
|
||||
)
|
||||
|
||||
// FightActionEnvelope 是统一入站动作结构。
|
||||
// 约定:
|
||||
// 1. actorIndex 始终表示发起方所在的我方槽位。
|
||||
// 2. targetIndex 始终表示目标在所属阵营内的槽位。
|
||||
// 3. targetRelation 用来区分 targetIndex 属于敌方、自己还是队友。
|
||||
type FightActionEnvelope struct {
|
||||
// ActionType 当前动作类型,例如 skill、item、change、escape、chat。
|
||||
ActionType FightActionType `json:"actionType"`
|
||||
// ActorIndex 发起动作的我方槽位。
|
||||
ActorIndex int `json:"actorIndex"`
|
||||
// TargetIndex 目标在所属阵营中的槽位下标。
|
||||
TargetIndex int `json:"targetIndex"`
|
||||
// TargetRelation 目标关系:0=对方,1=自己,2=队友。
|
||||
TargetRelation uint8 `json:"targetRelation,omitempty"`
|
||||
// SkillID 技能 ID;仅技能动作使用。
|
||||
SkillID uint32 `json:"skillId,omitempty"`
|
||||
// ItemID 道具 ID;仅道具动作使用。
|
||||
ItemID uint32 `json:"itemId,omitempty"`
|
||||
// CatchTime 精灵实例 ID;切宠或部分道具动作使用。
|
||||
CatchTime uint32 `json:"catchTime,omitempty"`
|
||||
// Escape 是否为逃跑动作;主要用于协议层兼容和调试。
|
||||
Escape bool `json:"escape,omitempty"`
|
||||
// Chat 聊天内容;仅聊天动作使用。
|
||||
Chat string `json:"chat,omitempty"`
|
||||
// AtkType 前端技能目标类型兜底值,沿用技能表 AtkType 定义。
|
||||
AtkType uint8 `json:"atkType,omitempty"`
|
||||
}
|
||||
|
||||
// NewSkillActionEnvelope 构造技能动作 envelope。
|
||||
func NewSkillActionEnvelope(skillID uint32, actorIndex, targetIndex int, targetRelation, atkType uint8) FightActionEnvelope {
|
||||
return FightActionEnvelope{
|
||||
ActionType: FightActionTypeSkill,
|
||||
ActorIndex: actorIndex,
|
||||
TargetIndex: targetIndex,
|
||||
TargetRelation: targetRelation,
|
||||
SkillID: skillID,
|
||||
AtkType: atkType,
|
||||
}
|
||||
}
|
||||
|
||||
// NewItemActionEnvelope 构造道具动作 envelope。
|
||||
func NewItemActionEnvelope(catchTime, itemID uint32, actorIndex, targetIndex int, targetRelation uint8) FightActionEnvelope {
|
||||
return FightActionEnvelope{
|
||||
ActionType: FightActionTypeItem,
|
||||
ActorIndex: actorIndex,
|
||||
TargetIndex: targetIndex,
|
||||
TargetRelation: targetRelation,
|
||||
ItemID: itemID,
|
||||
CatchTime: catchTime,
|
||||
}
|
||||
}
|
||||
|
||||
// NewChangeActionEnvelope 构造切宠动作 envelope。
|
||||
func NewChangeActionEnvelope(catchTime uint32, actorIndex int) FightActionEnvelope {
|
||||
return FightActionEnvelope{
|
||||
ActionType: FightActionTypeChange,
|
||||
ActorIndex: actorIndex,
|
||||
CatchTime: catchTime,
|
||||
}
|
||||
}
|
||||
|
||||
// NewEscapeActionEnvelope 构造逃跑动作 envelope。
|
||||
func NewEscapeActionEnvelope() FightActionEnvelope {
|
||||
return FightActionEnvelope{
|
||||
ActionType: FightActionTypeEscape,
|
||||
Escape: true,
|
||||
}
|
||||
}
|
||||
|
||||
// NewChatActionEnvelope 构造聊天动作 envelope。
|
||||
func NewChatActionEnvelope(chat string) FightActionEnvelope {
|
||||
return FightActionEnvelope{
|
||||
ActionType: FightActionTypeChat,
|
||||
Chat: chat,
|
||||
}
|
||||
}
|
||||
|
||||
// normalizedTargetRelation 根据 TargetRelation 和 AtkType 兜底出最终目标关系。
|
||||
func (e FightActionEnvelope) normalizedTargetRelation() uint8 {
|
||||
if e.TargetRelation <= SkillTargetAlly {
|
||||
return e.TargetRelation
|
||||
}
|
||||
switch e.AtkType {
|
||||
case 3:
|
||||
return SkillTargetSelf
|
||||
case 1:
|
||||
return SkillTargetAlly
|
||||
default:
|
||||
return SkillTargetOpponent
|
||||
}
|
||||
}
|
||||
|
||||
// EncodedTargetIndex 把统一结构里的目标信息编码成现有 FightC 内部使用的目标格式。
|
||||
func (e FightActionEnvelope) EncodedTargetIndex() int {
|
||||
targetIndex := e.TargetIndex
|
||||
switch e.normalizedTargetRelation() {
|
||||
case SkillTargetSelf:
|
||||
targetIndex = e.ActorIndex
|
||||
return EncodeTargetIndex(targetIndex, false)
|
||||
case SkillTargetAlly:
|
||||
return EncodeTargetIndex(targetIndex, false)
|
||||
default:
|
||||
return EncodeTargetIndex(targetIndex, true)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleActionEnvelope 把统一动作结构派发到现有 FightC 的 indexed 接口上。
|
||||
func (f *FightC) HandleActionEnvelope(c common.PlayerI, envelope FightActionEnvelope) {
|
||||
if f == nil || c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch envelope.ActionType {
|
||||
case FightActionTypeSkill:
|
||||
f.UseSkillAt(c, envelope.SkillID, envelope.ActorIndex, envelope.EncodedTargetIndex())
|
||||
case FightActionTypeItem:
|
||||
f.UseItemAt(c, envelope.CatchTime, envelope.ItemID, envelope.ActorIndex, envelope.EncodedTargetIndex())
|
||||
case FightActionTypeChange:
|
||||
f.ChangePetAt(c, envelope.CatchTime, envelope.ActorIndex)
|
||||
case FightActionTypeEscape:
|
||||
f.Over(c, model.BattleOverReason.PlayerEscape)
|
||||
case FightActionTypeChat:
|
||||
f.Chat(c, envelope.Chat)
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,35 @@ func (t *BattlePetEntity) Alive() bool {
|
||||
|
||||
}
|
||||
|
||||
func (t *BattlePetEntity) AddBattleAttr(attr int, value uint32) {
|
||||
if t == nil || value == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
switch attr {
|
||||
case 0:
|
||||
t.Info.MaxHp += value
|
||||
t.Info.Hp += value
|
||||
case 1, 2, 3, 4, 5:
|
||||
t.Info.Prop[attr-1] += value
|
||||
}
|
||||
}
|
||||
|
||||
func (t *BattlePetEntity) ApplyInitEffectBonus(effect model.PetEffectInfo) {
|
||||
if t == nil || effect.EID != 26 || len(effect.Args) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i+1 < len(effect.Args); i += 2 {
|
||||
attr := effect.Args[i]
|
||||
value := effect.Args[i+1]
|
||||
if value <= 0 {
|
||||
continue
|
||||
}
|
||||
t.AddBattleAttr(attr, uint32(value))
|
||||
}
|
||||
}
|
||||
|
||||
// 创建精灵实例
|
||||
func CreateBattlePetEntity(info model.PetInfo) *BattlePetEntity {
|
||||
ret := &BattlePetEntity{}
|
||||
|
||||
114
logic/service/fight/info/unified_info.go
Normal file
114
logic/service/fight/info/unified_info.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package info
|
||||
|
||||
import "blazing/modules/player/model"
|
||||
|
||||
// FightStatePhase 表示统一战斗状态包的阶段。
|
||||
type FightStatePhase string
|
||||
|
||||
const (
|
||||
// FightStatePhaseStart 表示开战快照。
|
||||
FightStatePhaseStart FightStatePhase = "start"
|
||||
// FightStatePhaseSkillHurt 表示技能结算后的伤害快照。
|
||||
FightStatePhaseSkillHurt FightStatePhase = "skill_hurt"
|
||||
// FightStatePhaseChange 表示切宠快照。
|
||||
FightStatePhaseChange FightStatePhase = "change"
|
||||
// FightStatePhaseOver 表示战斗结束快照。
|
||||
FightStatePhaseOver FightStatePhase = "over"
|
||||
// FightStatePhaseLoad 表示加载进度快照。
|
||||
FightStatePhaseLoad FightStatePhase = "load"
|
||||
// FightStatePhaseChat 表示战斗内聊天快照。
|
||||
FightStatePhaseChat FightStatePhase = "chat"
|
||||
)
|
||||
|
||||
// FighterState 是统一的战位快照。
|
||||
// position 表示该 side 下的槽位编号;actorIndex/targetIndex 与其一一对应。
|
||||
type FighterState struct {
|
||||
// Side 阵营标识:1=我方,2=敌方。
|
||||
Side int `json:"side"`
|
||||
// Position 战位下标;在各自 side 内部从 0 开始编号。
|
||||
Position int `json:"position"`
|
||||
// UserID 当前战位所属玩家 ID;野怪/NPC 通常为 0。
|
||||
UserID uint32 `json:"userId"`
|
||||
// ControllerUserID 当前上场精灵的实际操作者 ID;组队时可与 UserID 联合定位操作者和战位归属。
|
||||
ControllerUserID uint32 `json:"controllerUserId"`
|
||||
// PetID 当前上场精灵的物种/配置 ID。
|
||||
PetID uint32 `json:"petId"`
|
||||
// CatchTime 当前上场精灵的唯一实例 ID,可理解为这只精灵在玩家背包中的唯一标识。
|
||||
CatchTime uint32 `json:"catchTime"`
|
||||
// Name 当前上场精灵名字。
|
||||
Name string `json:"name,omitempty"`
|
||||
// HP 当前生命值。
|
||||
HP uint32 `json:"hp"`
|
||||
// MaxHP 最大生命值。
|
||||
MaxHP uint32 `json:"maxHp"`
|
||||
// Level 当前等级。
|
||||
Level uint32 `json:"level"`
|
||||
// Anger 怒气值;当前服务端主链路暂未实际填充时默认为 0,先为协议对齐预留。
|
||||
Anger uint32 `json:"anger"`
|
||||
// Status 当前异常或增益状态回合数组;下标语义沿用现有战斗状态定义。
|
||||
Status [20]int8 `json:"status"`
|
||||
// Prop 当前能力等级变化数组:攻击、防御、特攻、特防、速度、命中。
|
||||
Prop [6]int8 `json:"prop"`
|
||||
// Skills 当前可见技能列表,包含技能 ID 和当前 PP 等信息。
|
||||
Skills []model.SkillInfo `json:"skills,omitempty"`
|
||||
}
|
||||
|
||||
// FightStateMeta 是统一状态包的公共元数据。
|
||||
type FightStateMeta struct {
|
||||
// Round 当前回合数。
|
||||
Round uint32 `json:"round"`
|
||||
// Weather 当前天气或场地编号;当前主链路未填充时可为 0。
|
||||
Weather uint32 `json:"weather,omitempty"`
|
||||
// WinnerID 当前已确定的胜者 ID;未结束时通常为 0。
|
||||
WinnerID uint32 `json:"winnerId,omitempty"`
|
||||
// Reason 当前已确定的结束原因;未结束时通常为 0。
|
||||
Reason model.EnumBattleOverReason `json:"reason,omitempty"`
|
||||
// LegacyCmd 对应旧协议命令号,便于新旧包对照和过渡期调试。
|
||||
LegacyCmd uint32 `json:"legacyCmd,omitempty"`
|
||||
}
|
||||
|
||||
// FightSkillHurtState 保存技能结算阶段的详细战报。
|
||||
type FightSkillHurtState struct {
|
||||
// Left 我方阵营本次技能结算后的攻击值快照列表。
|
||||
Left []model.AttackValue `json:"left,omitempty"`
|
||||
// Right 敌方阵营本次技能结算后的攻击值快照列表。
|
||||
Right []model.AttackValue `json:"right,omitempty"`
|
||||
}
|
||||
|
||||
// FightLoadState 保存加载进度信息。
|
||||
type FightLoadState struct {
|
||||
// UserID 当前上报加载进度的玩家 ID。
|
||||
UserID uint32 `json:"userId"`
|
||||
// Percent 当前加载百分比。
|
||||
Percent uint32 `json:"percent"`
|
||||
}
|
||||
|
||||
// FightChatState 保存战斗内聊天信息。
|
||||
type FightChatState struct {
|
||||
// SenderID 发言玩家 ID。
|
||||
SenderID uint32 `json:"senderId"`
|
||||
// SenderNickname 发言玩家昵称。
|
||||
SenderNickname string `json:"senderNickname"`
|
||||
// Message 聊天内容。
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// FightStateEnvelope 是统一出站状态结构。
|
||||
type FightStateEnvelope struct {
|
||||
// Phase 当前下发阶段,例如 start、skill_hurt、change、over、load、chat。
|
||||
Phase FightStatePhase `json:"phase"`
|
||||
// Left 我方阵营当前所有战位快照。
|
||||
Left []FighterState `json:"left,omitempty"`
|
||||
// Right 敌方阵营当前所有战位快照。
|
||||
Right []FighterState `json:"right,omitempty"`
|
||||
// Meta 当前阶段共用的元数据,如回合号、胜方、结束原因、旧协议命令号。
|
||||
Meta FightStateMeta `json:"meta"`
|
||||
// SkillHurt 技能结算阶段附带的详细战报;仅 phase=skill_hurt 时使用。
|
||||
SkillHurt *FightSkillHurtState `json:"skillHurt,omitempty"`
|
||||
// Change 切宠阶段附带的切宠详情;仅 phase=change 时使用。
|
||||
Change *ChangePetInfo `json:"change,omitempty"`
|
||||
// Load 加载阶段附带的进度信息;仅 phase=load 时使用。
|
||||
Load *FightLoadState `json:"load,omitempty"`
|
||||
// Chat 聊天阶段附带的聊天内容;仅 phase=chat 时使用。
|
||||
Chat *FightChatState `json:"chat,omitempty"`
|
||||
}
|
||||
@@ -2,51 +2,64 @@ package input
|
||||
|
||||
import (
|
||||
"blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/player"
|
||||
configmodel "blazing/modules/config/model"
|
||||
playermodel "blazing/modules/player/model"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/util/grand"
|
||||
)
|
||||
|
||||
// Shuffle 打乱切片顺序,使用 Fisher-Yates 洗牌算法,泛型支持任意类型
|
||||
func Shuffle[T any](slice []T) {
|
||||
// 从后往前遍历,逐个交换
|
||||
for i := len(slice) - 1; i > 0; i-- {
|
||||
// 生成 0 到 i 之间的随机索引
|
||||
j := grand.Intn(i + 1)
|
||||
// 交换 i 和 j 位置的元素
|
||||
slice[i], slice[j] = slice[j], slice[i]
|
||||
}
|
||||
}
|
||||
|
||||
func (our *Input) GetAction() {
|
||||
|
||||
next := our.Exec(func(t Effect) bool {
|
||||
|
||||
return t.HookAction()
|
||||
})
|
||||
|
||||
scriptCtx := buildBossHookActionContext(our, next)
|
||||
if aiPlayer, ok := our.Player.(*player.AI_player); ok && aiPlayer.BossScript != "" {
|
||||
scriptBoss := &configmodel.BossConfig{Script: aiPlayer.BossScript}
|
||||
nextByScript, err := scriptBoss.RunHookActionScript(scriptCtx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
next = nextByScript
|
||||
}
|
||||
|
||||
if !next {
|
||||
return
|
||||
}
|
||||
// 获取己方当前宠物和对方当前宠物
|
||||
|
||||
if applyBossScriptAction(our, scriptCtx) {
|
||||
return
|
||||
}
|
||||
|
||||
selfPet := our.FightC.GetCurrPET(our.Player)
|
||||
//没血就切换精灵
|
||||
if selfPet == nil {
|
||||
return
|
||||
}
|
||||
if selfPet.Info.Hp <= 0 {
|
||||
for _, v := range our.AllPet {
|
||||
if v.Info.Hp > 0 {
|
||||
// println("AI出手,切换宠物")
|
||||
our.FightC.ChangePet(our.Player, v.Info.CatchTime)
|
||||
return
|
||||
}
|
||||
}
|
||||
// 如果没有可用宠物,则直接返回,不执行任何操作
|
||||
return
|
||||
}
|
||||
//oppPet := opp.FightC.GetCurrPET(opp.Player)
|
||||
skills := selfPet.Skills
|
||||
|
||||
// 空技能列表直接返回,避免错误
|
||||
skills := selfPet.Skills
|
||||
if len(skills) == 0 {
|
||||
return
|
||||
}
|
||||
//println("AI出手,选择技能")
|
||||
|
||||
var usedskill *info.SkillEntity
|
||||
for _, s := range skills {
|
||||
if s == nil {
|
||||
@@ -55,30 +68,25 @@ func (our *Input) GetAction() {
|
||||
if !s.CanUse() {
|
||||
continue
|
||||
}
|
||||
// 计算技能对对方的伤害(假设CalculatePower返回伤害值,或需从技能中获取)
|
||||
s.DamageValue = our.CalculatePower(our.Opp, s)
|
||||
|
||||
oppPet := our.Opp.CurrentPet()
|
||||
if oppPet == nil {
|
||||
continue
|
||||
}
|
||||
// 判断是否能秒杀(伤害 >= 对方当前生命值)
|
||||
if s.DamageValue.Cmp(oppPet.GetHP()) != -1 { // 假设oppPet.HP为对方当前剩余生命值
|
||||
|
||||
if s.DamageValue.Cmp(oppPet.GetHP()) != -1 {
|
||||
if usedskill != nil {
|
||||
if s.DamageValue.Cmp(usedskill.DamageValue) != -1 {
|
||||
usedskill = s
|
||||
}
|
||||
|
||||
} else {
|
||||
usedskill = s
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Shuffle(skills)
|
||||
if usedskill == nil {
|
||||
|
||||
for _, s := range skills {
|
||||
if s == nil {
|
||||
continue
|
||||
@@ -87,14 +95,121 @@ func (our *Input) GetAction() {
|
||||
continue
|
||||
}
|
||||
usedskill = s
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if usedskill != nil {
|
||||
our.FightC.UseSkill(our.Player, uint32(usedskill.XML.ID))
|
||||
} else {
|
||||
our.FightC.UseSkill(our.Player, 0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func buildBossHookActionContext(our *Input, hookAction bool) *configmodel.BossHookActionContext {
|
||||
ctx := &configmodel.BossHookActionContext{
|
||||
HookAction: hookAction,
|
||||
Action: "auto",
|
||||
}
|
||||
ctx.UseSkillFn = func(skillID uint32) {
|
||||
ctx.Action = "skill"
|
||||
ctx.SkillID = skillID
|
||||
}
|
||||
ctx.SwitchPetFn = func(catchTime uint32) {
|
||||
ctx.Action = "switch"
|
||||
ctx.CatchTime = catchTime
|
||||
}
|
||||
|
||||
if our == nil || our.FightC == nil || our.Player == nil {
|
||||
return ctx
|
||||
}
|
||||
|
||||
overInfo := our.FightC.GetOverInfo()
|
||||
ctx.Round = overInfo.Round
|
||||
ctx.IsFirst = our.FightC.IsFirst(our.Player)
|
||||
|
||||
if selfPet := our.CurrentPet(); selfPet != nil {
|
||||
ctx.Our = &configmodel.BossHookPetContext{
|
||||
PetID: selfPet.Info.ID,
|
||||
CatchTime: selfPet.Info.CatchTime,
|
||||
Hp: selfPet.Info.Hp,
|
||||
MaxHp: selfPet.Info.MaxHp,
|
||||
}
|
||||
ctx.Skills = make([]configmodel.BossHookSkillContext, 0, len(selfPet.Skills))
|
||||
for _, s := range selfPet.Skills {
|
||||
if s == nil || s.Info == nil {
|
||||
continue
|
||||
}
|
||||
ctx.Skills = append(ctx.Skills, configmodel.BossHookSkillContext{
|
||||
SkillID: s.Info.ID,
|
||||
PP: s.Info.PP,
|
||||
CanUse: s.CanUse(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if our.AttackValue != nil {
|
||||
ctx.OurAttack = convertAttackValue(our.AttackValue)
|
||||
}
|
||||
if our.Opp != nil {
|
||||
if oppPet := our.Opp.CurrentPet(); oppPet != nil {
|
||||
ctx.Opp = &configmodel.BossHookPetContext{
|
||||
PetID: oppPet.Info.ID,
|
||||
CatchTime: oppPet.Info.CatchTime,
|
||||
Hp: oppPet.Info.Hp,
|
||||
MaxHp: oppPet.Info.MaxHp,
|
||||
}
|
||||
}
|
||||
if our.Opp.AttackValue != nil {
|
||||
ctx.OppAttack = convertAttackValue(our.Opp.AttackValue)
|
||||
}
|
||||
}
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func convertAttackValue(src *playermodel.AttackValue) *configmodel.BossHookAttackContext {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
status := make([]int8, len(src.Status))
|
||||
for i := range src.Status {
|
||||
status[i] = src.Status[i]
|
||||
}
|
||||
prop := make([]int8, len(src.Prop))
|
||||
for i := range src.Prop {
|
||||
prop[i] = src.Prop[i]
|
||||
}
|
||||
|
||||
return &configmodel.BossHookAttackContext{
|
||||
SkillID: src.SkillID,
|
||||
AttackTime: src.AttackTime,
|
||||
IsCritical: src.IsCritical,
|
||||
LostHp: src.LostHp,
|
||||
GainHp: src.GainHp,
|
||||
RemainHp: src.RemainHp,
|
||||
MaxHp: src.MaxHp,
|
||||
State: src.State,
|
||||
Offensive: src.Offensive,
|
||||
Status: status,
|
||||
Prop: prop,
|
||||
}
|
||||
}
|
||||
|
||||
func applyBossScriptAction(our *Input, ctx *configmodel.BossHookActionContext) bool {
|
||||
if our == nil || ctx == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
switch strings.ToLower(strings.TrimSpace(ctx.Action)) {
|
||||
case "", "auto":
|
||||
return false
|
||||
case "skill", "use_skill", "useskill":
|
||||
our.FightC.UseSkill(our.Player, ctx.SkillID)
|
||||
return true
|
||||
case "switch", "change_pet", "changepet":
|
||||
our.FightC.ChangePet(our.Player, ctx.CatchTime)
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +167,7 @@ func (our *Input) SortPet() {
|
||||
|
||||
t.Duration(-1)
|
||||
|
||||
s.ApplyInitEffectBonus(e1)
|
||||
our.AddEffect(our, t)
|
||||
}
|
||||
|
||||
@@ -338,9 +339,6 @@ func (our *Input) Parseskill(skill *action.SelectSkillAction) {
|
||||
args := xmlres.EffectArgs[v]
|
||||
t := our.InitEffect(EffectType.Skill, v, temparg[:args]...)
|
||||
|
||||
|
||||
|
||||
|
||||
//这里是给双方添加buff
|
||||
if t != nil {
|
||||
// t.SetArgs(our, temparg[:args]...) //设置入参,施加方永远是我方
|
||||
|
||||
55
logic/service/fight/input/team.go
Normal file
55
logic/service/fight/input/team.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package input
|
||||
|
||||
// TeamSlots 返回当前输入所在阵营的全部有效战斗位视图。
|
||||
func (our *Input) TeamSlots() []*Input {
|
||||
if our == nil {
|
||||
return nil
|
||||
}
|
||||
if len(our.Team) == 0 {
|
||||
return []*Input{our}
|
||||
}
|
||||
slots := make([]*Input, 0, len(our.Team))
|
||||
for _, teammate := range our.Team {
|
||||
if teammate == nil {
|
||||
continue
|
||||
}
|
||||
slots = append(slots, teammate)
|
||||
}
|
||||
return slots
|
||||
}
|
||||
|
||||
// Teammates 返回队友列表,不包含自己。
|
||||
func (our *Input) Teammates() []*Input {
|
||||
if our == nil {
|
||||
return nil
|
||||
}
|
||||
teammates := make([]*Input, 0, len(our.Team))
|
||||
for _, teammate := range our.TeamSlots() {
|
||||
if teammate == nil || teammate == our {
|
||||
continue
|
||||
}
|
||||
teammates = append(teammates, teammate)
|
||||
}
|
||||
return teammates
|
||||
}
|
||||
|
||||
// LivingTeammates 返回当前仍有存活出战精灵的队友列表。
|
||||
func (our *Input) LivingTeammates() []*Input {
|
||||
if our == nil {
|
||||
return nil
|
||||
}
|
||||
teammates := make([]*Input, 0, len(our.Team))
|
||||
for _, teammate := range our.Teammates() {
|
||||
currentPet := teammate.CurrentPet()
|
||||
if currentPet == nil || currentPet.Info.Hp == 0 {
|
||||
continue
|
||||
}
|
||||
teammates = append(teammates, teammate)
|
||||
}
|
||||
return teammates
|
||||
}
|
||||
|
||||
// HasLivingTeammate 用于快速判断当前战斗位是否还有存活队友。
|
||||
func (our *Input) HasLivingTeammate() bool {
|
||||
return len(our.LivingTeammates()) > 0
|
||||
}
|
||||
30
logic/service/fight/input/team_test.go
Normal file
30
logic/service/fight/input/team_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package input
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
fightinfo "blazing/logic/service/fight/info"
|
||||
"blazing/modules/player/model"
|
||||
)
|
||||
|
||||
func TestLivingTeammatesFiltersSelfAndDeadSlots(t *testing.T) {
|
||||
owner := &Input{CurPet: []*fightinfo.BattlePetEntity{{Info: model.PetInfo{Hp: 10}}}}
|
||||
aliveMate := &Input{CurPet: []*fightinfo.BattlePetEntity{{Info: model.PetInfo{Hp: 5}}}}
|
||||
deadMate := &Input{CurPet: []*fightinfo.BattlePetEntity{{Info: model.PetInfo{Hp: 0}}}}
|
||||
|
||||
team := []*Input{owner, aliveMate, deadMate}
|
||||
owner.Team = team
|
||||
aliveMate.Team = team
|
||||
deadMate.Team = team
|
||||
|
||||
teammates := owner.LivingTeammates()
|
||||
if len(teammates) != 1 {
|
||||
t.Fatalf("expected 1 living teammate, got %d", len(teammates))
|
||||
}
|
||||
if teammates[0] != aliveMate {
|
||||
t.Fatalf("expected alive teammate to be returned")
|
||||
}
|
||||
if owner.HasLivingTeammate() != true {
|
||||
t.Fatalf("expected owner to detect living teammate")
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,26 @@ import (
|
||||
"github.com/jinzhu/copier"
|
||||
)
|
||||
|
||||
func consumeLimitedPetEffects(pet *model.PetInfo) {
|
||||
if pet == nil || len(pet.EffectInfo) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
next := pet.EffectInfo[:0]
|
||||
for _, eff := range pet.EffectInfo {
|
||||
if eff.Status == 2 {
|
||||
if eff.LeftCount > 0 {
|
||||
eff.LeftCount--
|
||||
}
|
||||
if eff.LeftCount == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
next = append(next, eff)
|
||||
}
|
||||
pet.EffectInfo = next
|
||||
}
|
||||
|
||||
func (f *FightC) battleLoop() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil { // 恢复 panic,err 为 panic 错误值
|
||||
@@ -69,26 +89,28 @@ func (f *FightC) battleLoop() {
|
||||
tt.Alive(false) //将所有属性变化失效掉
|
||||
return true
|
||||
})
|
||||
if f.Info.Mode != info.BattleMode.PET_MELEE { //不是乱斗,传回血量
|
||||
for i := 0; i < len(ff.AllPet); i++ {
|
||||
for j := 0; j < len(ff.Player.GetInfo().PetList); j++ {
|
||||
if ff.Player.GetInfo().PetList[j].CatchTime == ff.AllPet[i].Info.CatchTime {
|
||||
|
||||
if ff.UserID == f.WinnerId {
|
||||
currentPet := ff.CurrentPet()
|
||||
if currentPet != nil && currentPet.Info.CatchTime == ff.Player.GetInfo().PetList[j].CatchTime {
|
||||
f.Winpet = &ff.Player.GetInfo().PetList[j]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ff.Player.GetInfo().PetList[j].Hp = utils.Min(ff.Player.GetInfo().PetList[j].MaxHp, ff.AllPet[i].Info.Hp)
|
||||
ff.Player.GetInfo().PetList[j].SkillList = ff.AllPet[i].Info.SkillList
|
||||
}
|
||||
|
||||
for i := 0; i < len(ff.AllPet); i++ {
|
||||
consumeLimitedPetEffects(&ff.AllPet[i].Info)
|
||||
for j := 0; j < len(ff.Player.GetInfo().PetList); j++ {
|
||||
if ff.Player.GetInfo().PetList[j].CatchTime != ff.AllPet[i].Info.CatchTime {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
ff.Player.GetInfo().PetList[j].EffectInfo = ff.AllPet[i].Info.EffectInfo
|
||||
if f.Info.Mode == info.BattleMode.PET_MELEE {
|
||||
continue
|
||||
}
|
||||
|
||||
if ff.UserID == f.WinnerId {
|
||||
currentPet := ff.CurrentPet()
|
||||
if currentPet != nil && currentPet.Info.CatchTime == ff.Player.GetInfo().PetList[j].CatchTime {
|
||||
f.Winpet = &ff.Player.GetInfo().PetList[j]
|
||||
}
|
||||
}
|
||||
|
||||
ff.Player.GetInfo().PetList[j].Hp = utils.Min(ff.Player.GetInfo().PetList[j].MaxHp, ff.AllPet[i].Info.Hp)
|
||||
ff.Player.GetInfo().PetList[j].SkillList = ff.AllPet[i].Info.SkillList
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
@@ -162,7 +184,8 @@ func (f *FightC) collectPlayerActions(expectedSlots map[actionSlotKey]struct{})
|
||||
|
||||
waitr := time.Duration(f.waittime)*time.Millisecond*10 + 30*time.Second
|
||||
|
||||
timeout := time.After(waitr)
|
||||
timeout := time.NewTimer(waitr)
|
||||
defer timeout.Stop()
|
||||
for len(actions) < len(expectedSlots) {
|
||||
|
||||
select {
|
||||
@@ -266,11 +289,12 @@ func (f *FightC) collectPlayerActions(expectedSlots map[actionSlotKey]struct{})
|
||||
actions[key] = paction
|
||||
//fmt.Println("玩家执行动作:", pid, paction.Priority())
|
||||
|
||||
case <-timeout:
|
||||
case <-timeout.C:
|
||||
r := f.handleTimeout(expectedSlots, actions)
|
||||
if r {
|
||||
return flattenActionMap(actions)
|
||||
}
|
||||
timeout.Reset(waitr)
|
||||
|
||||
}
|
||||
}
|
||||
@@ -287,7 +311,7 @@ func (f *FightC) handleTimeout(expectedSlots map[actionSlotKey]struct{}, actions
|
||||
}
|
||||
player := f.getPlayerByID(key.PlayerID)
|
||||
if player != nil {
|
||||
go f.UseSkillAt(player, 0, key.ActorIndex, 0)
|
||||
f.UseSkillAt(player, 0, key.ActorIndex, 0)
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -154,7 +154,7 @@ func buildFight(opts *fightBuildOptions) (*FightC, errorcode.ErrorCode) {
|
||||
if ai.CanCapture > 0 {
|
||||
opp.CanCapture = ai.CanCapture
|
||||
}
|
||||
opp.AttackValue.Prop = ai.Prop
|
||||
ai.ApplyBattleProps(opp.AttackValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ const (
|
||||
queueTTL = 12 * time.Second
|
||||
banPickTimeout = 45 * time.Second
|
||||
banPickStartCmd = 2461
|
||||
battleLevelCap = 100
|
||||
)
|
||||
|
||||
type localQueueTicket struct {
|
||||
@@ -665,7 +666,11 @@ func resolveBattlePets(catchTimes []uint32, limit int) []model.PetInfo {
|
||||
if pet == nil || pet.Data.ID == 0 {
|
||||
continue
|
||||
}
|
||||
result = append(result, pet.Data)
|
||||
petInfo := pet.Data
|
||||
if petInfo.Level > battleLevelCap {
|
||||
petInfo.Level = battleLevelCap
|
||||
}
|
||||
result = append(result, petInfo)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
113
logic/service/fight/unified_state.go
Normal file
113
logic/service/fight/unified_state.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package fight
|
||||
|
||||
import (
|
||||
"blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/fight/input"
|
||||
)
|
||||
|
||||
// BuildFightStateStartEnvelope 构造开战阶段的统一状态包。
|
||||
func (f *FightC) BuildFightStateStartEnvelope() info.FightStateEnvelope {
|
||||
return f.buildFightStateEnvelope(info.FightStatePhaseStart, 2504)
|
||||
}
|
||||
|
||||
// BuildFightStateSkillHurtEnvelope 构造技能结算阶段的统一状态包。
|
||||
func (f *FightC) BuildFightStateSkillHurtEnvelope() info.FightStateEnvelope {
|
||||
envelope := f.buildFightStateEnvelope(info.FightStatePhaseSkillHurt, 2505)
|
||||
envelope.SkillHurt = &info.FightSkillHurtState{
|
||||
Left: f.collectAttackValues(f.Our),
|
||||
Right: f.collectAttackValues(f.Opp),
|
||||
}
|
||||
return envelope
|
||||
}
|
||||
|
||||
// BuildFightStateChangeEnvelope 构造切宠阶段的统一状态包。
|
||||
func (f *FightC) BuildFightStateChangeEnvelope(change info.ChangePetInfo) info.FightStateEnvelope {
|
||||
envelope := f.buildFightStateEnvelope(info.FightStatePhaseChange, 2407)
|
||||
envelope.Change = &change
|
||||
return envelope
|
||||
}
|
||||
|
||||
// BuildFightStateOverEnvelope 构造结束阶段的统一状态包。
|
||||
func (f *FightC) BuildFightStateOverEnvelope() info.FightStateEnvelope {
|
||||
envelope := f.buildFightStateEnvelope(info.FightStatePhaseOver, 2506)
|
||||
envelope.Meta.WinnerID = f.FightOverInfo.WinnerId
|
||||
envelope.Meta.Reason = f.FightOverInfo.Reason
|
||||
return envelope
|
||||
}
|
||||
|
||||
// BuildFightStateLoadEnvelope 构造加载阶段的统一状态包。
|
||||
func (f *FightC) BuildFightStateLoadEnvelope(userID, percent uint32) info.FightStateEnvelope {
|
||||
envelope := f.buildFightStateEnvelope(info.FightStatePhaseLoad, 2441)
|
||||
envelope.Load = &info.FightLoadState{
|
||||
UserID: userID,
|
||||
Percent: percent,
|
||||
}
|
||||
return envelope
|
||||
}
|
||||
|
||||
// BuildFightStateChatEnvelope 构造聊天阶段的统一状态包。
|
||||
func (f *FightC) BuildFightStateChatEnvelope(senderID uint32, senderNickname, message string) info.FightStateEnvelope {
|
||||
envelope := f.buildFightStateEnvelope(info.FightStatePhaseChat, 50002)
|
||||
envelope.Chat = &info.FightChatState{
|
||||
SenderID: senderID,
|
||||
SenderNickname: senderNickname,
|
||||
Message: message,
|
||||
}
|
||||
return envelope
|
||||
}
|
||||
|
||||
// buildFightStateEnvelope 组装左右两侧通用快照和元数据。
|
||||
func (f *FightC) buildFightStateEnvelope(phase info.FightStatePhase, legacyCmd uint32) info.FightStateEnvelope {
|
||||
if f == nil {
|
||||
return info.FightStateEnvelope{Phase: phase}
|
||||
}
|
||||
return info.FightStateEnvelope{
|
||||
Phase: phase,
|
||||
Left: snapshotFighterStates(SideOur, f.Our),
|
||||
Right: snapshotFighterStates(SideOpp, f.Opp),
|
||||
Meta: info.FightStateMeta{
|
||||
Round: uint32(f.Round),
|
||||
WinnerID: f.FightOverInfo.WinnerId,
|
||||
Reason: f.FightOverInfo.Reason,
|
||||
LegacyCmd: legacyCmd,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// snapshotFighterStates 把指定侧的战斗位数组转成统一 fighter 快照。
|
||||
func snapshotFighterStates(side int, fighters []*input.Input) []info.FighterState {
|
||||
states := make([]info.FighterState, 0, len(fighters))
|
||||
for position, fighter := range fighters {
|
||||
if fighter == nil {
|
||||
continue
|
||||
}
|
||||
state := info.FighterState{
|
||||
Side: side,
|
||||
Position: position,
|
||||
}
|
||||
if fighter.Player != nil && fighter.Player.GetInfo() != nil {
|
||||
state.UserID = fighter.Player.GetInfo().UserID
|
||||
}
|
||||
if fighter.AttackValue != nil {
|
||||
state.Status = fighter.AttackValue.Status
|
||||
state.Prop = fighter.AttackValue.Prop
|
||||
}
|
||||
currentPet := fighter.CurrentPet()
|
||||
if currentPet == nil {
|
||||
states = append(states, state)
|
||||
continue
|
||||
}
|
||||
state.ControllerUserID = currentPet.ControllerUserID
|
||||
state.PetID = currentPet.Info.ID
|
||||
state.CatchTime = currentPet.Info.CatchTime
|
||||
state.Name = currentPet.Info.Name
|
||||
state.HP = currentPet.Info.Hp
|
||||
state.MaxHP = currentPet.Info.MaxHp
|
||||
state.Level = currentPet.Info.Level
|
||||
if len(currentPet.Info.SkillList) > 0 {
|
||||
state.Skills = append(state.Skills, currentPet.Info.SkillList...)
|
||||
}
|
||||
states = append(states, state)
|
||||
}
|
||||
return states
|
||||
}
|
||||
113
logic/service/fight/unified_test.go
Normal file
113
logic/service/fight/unified_test.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package fight
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/logic/service/common"
|
||||
fightinfo "blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/fight/input"
|
||||
spaceinfo "blazing/logic/service/space/info"
|
||||
"blazing/modules/player/model"
|
||||
)
|
||||
|
||||
type stubPlayer struct {
|
||||
info model.PlayerInfo
|
||||
}
|
||||
|
||||
func (*stubPlayer) ApplyPetDisplayInfo(*spaceinfo.SimpleInfo) {}
|
||||
func (*stubPlayer) GetPlayerCaptureContext() *fightinfo.PlayerCaptureContext { return nil }
|
||||
func (*stubPlayer) Roll(int, int) (bool, float64, float64) { return false, 0, 0 }
|
||||
func (*stubPlayer) Getfightinfo() fightinfo.Fightinfo { return fightinfo.Fightinfo{} }
|
||||
func (*stubPlayer) ItemAdd(int64, int64) bool { return false }
|
||||
func (p *stubPlayer) GetInfo() *model.PlayerInfo { return &p.info }
|
||||
func (*stubPlayer) InvitePlayer(common.PlayerI) {}
|
||||
func (*stubPlayer) SetFightC(common.FightI) {}
|
||||
func (*stubPlayer) QuitFight() {}
|
||||
func (*stubPlayer) MessWin(bool) {}
|
||||
func (*stubPlayer) CanFight() errorcode.ErrorCode { return 0 }
|
||||
func (*stubPlayer) SendPackCmd(uint32, any) {}
|
||||
func (*stubPlayer) GetPetInfo(uint32) []model.PetInfo { return nil }
|
||||
|
||||
func TestFightActionEnvelopeEncodedTargetIndex(t *testing.T) {
|
||||
self := NewSkillActionEnvelope(1, 2, 0, SkillTargetSelf, 0)
|
||||
if got := self.EncodedTargetIndex(); got != EncodeTargetIndex(2, false) {
|
||||
t.Fatalf("expected self target to encode actor slot, got %d", got)
|
||||
}
|
||||
|
||||
ally := NewSkillActionEnvelope(1, 0, 1, SkillTargetAlly, 0)
|
||||
if got := ally.EncodedTargetIndex(); got != EncodeTargetIndex(1, false) {
|
||||
t.Fatalf("expected ally target to keep friendly slot, got %d", got)
|
||||
}
|
||||
|
||||
fallbackSelf := NewSkillActionEnvelope(1, 3, 0, 9, 3)
|
||||
if got := fallbackSelf.EncodedTargetIndex(); got != EncodeTargetIndex(3, false) {
|
||||
t.Fatalf("expected atkType=3 to fall back to self target, got %d", got)
|
||||
}
|
||||
|
||||
opponent := NewSkillActionEnvelope(1, 0, 2, SkillTargetOpponent, 0)
|
||||
if got := opponent.EncodedTargetIndex(); got != EncodeTargetIndex(2, true) {
|
||||
t.Fatalf("expected opponent target to stay on opposite side, got %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFightStateStartEnvelope(t *testing.T) {
|
||||
ourPlayer := &stubPlayer{info: model.PlayerInfo{UserID: 1001}}
|
||||
oppPlayer := &stubPlayer{info: model.PlayerInfo{UserID: 2002}}
|
||||
|
||||
our := input.NewInput(nil, ourPlayer)
|
||||
our.InitAttackValue()
|
||||
our.AttackValue.Prop[0] = 2
|
||||
our.AttackValue.Status[1] = 1
|
||||
ourPet := fightinfo.CreateBattlePetEntity(model.PetInfo{
|
||||
ID: 11,
|
||||
Name: "Alpha",
|
||||
Level: 20,
|
||||
Hp: 88,
|
||||
MaxHp: 100,
|
||||
CatchTime: 101,
|
||||
SkillList: []model.SkillInfo{{ID: 300, PP: 10}},
|
||||
})
|
||||
ourPet.BindController(ourPlayer.info.UserID)
|
||||
our.SetCurPetAt(0, ourPet)
|
||||
|
||||
opp := input.NewInput(nil, oppPlayer)
|
||||
opp.InitAttackValue()
|
||||
oppPet := fightinfo.CreateBattlePetEntity(model.PetInfo{
|
||||
ID: 22,
|
||||
Name: "Beta",
|
||||
Level: 21,
|
||||
Hp: 77,
|
||||
MaxHp: 110,
|
||||
CatchTime: 202,
|
||||
SkillList: []model.SkillInfo{{ID: 400, PP: 5}},
|
||||
})
|
||||
oppPet.BindController(oppPlayer.info.UserID)
|
||||
opp.SetCurPetAt(0, oppPet)
|
||||
|
||||
fc := &FightC{
|
||||
Our: []*input.Input{our},
|
||||
Opp: []*input.Input{opp},
|
||||
}
|
||||
fc.Round = 7
|
||||
|
||||
envelope := fc.BuildFightStateStartEnvelope()
|
||||
if envelope.Phase != fightinfo.FightStatePhaseStart {
|
||||
t.Fatalf("expected start phase, got %s", envelope.Phase)
|
||||
}
|
||||
if envelope.Meta.Round != 7 {
|
||||
t.Fatalf("expected round 7, got %d", envelope.Meta.Round)
|
||||
}
|
||||
if len(envelope.Left) != 1 || len(envelope.Right) != 1 {
|
||||
t.Fatalf("expected one fighter on each side, got left=%d right=%d", len(envelope.Left), len(envelope.Right))
|
||||
}
|
||||
if envelope.Left[0].UserID != 1001 || envelope.Left[0].PetID != 11 {
|
||||
t.Fatalf("unexpected left fighter snapshot: %+v", envelope.Left[0])
|
||||
}
|
||||
if envelope.Left[0].Prop[0] != 2 || envelope.Left[0].Status[1] != 1 {
|
||||
t.Fatalf("expected prop/status snapshot to be copied, got %+v %+v", envelope.Left[0].Prop, envelope.Left[0].Status)
|
||||
}
|
||||
if envelope.Right[0].UserID != 2002 || envelope.Right[0].CatchTime != 202 {
|
||||
t.Fatalf("unexpected right fighter snapshot: %+v", envelope.Right[0])
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package item
|
||||
|
||||
import (
|
||||
"blazing/common/data/xmlres"
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/common/utils"
|
||||
"blazing/modules/player/model"
|
||||
"strings"
|
||||
@@ -26,6 +27,11 @@ type SetHandler struct {
|
||||
Handler PetItemHandler
|
||||
}
|
||||
|
||||
var fallbackPetItemNewSeIdx = map[uint32]int{
|
||||
300741: 1103, // 瞬杀能量珠
|
||||
300854: 1103, // 瞬杀能量珠Ω
|
||||
}
|
||||
|
||||
// PetItemHandlerRegistry 道具处理器注册器
|
||||
type PetItemHandlerRegistry struct {
|
||||
exactHandlers map[uint32]PetItemHandler // 精确ID映射
|
||||
@@ -111,6 +117,91 @@ func nvfunc(itemid uint32, onpet *model.PetInfo) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func resolvePetItemNewSeIdx(itemid uint32) (itemCfg xmlres.Item, newSeIdx int, ok bool) {
|
||||
itemCfg, ok = xmlres.ItemsMAP[int(itemid)]
|
||||
if ok && itemCfg.NewSeIdx != 0 {
|
||||
return itemCfg, itemCfg.NewSeIdx, true
|
||||
}
|
||||
|
||||
if newSeIdx, ok = fallbackPetItemNewSeIdx[itemid]; ok {
|
||||
return itemCfg, newSeIdx, true
|
||||
}
|
||||
|
||||
for idx, effectCfg := range xmlres.EffectMAP {
|
||||
if effectCfg.ItemId == nil || gconv.Uint32(*effectCfg.ItemId) != itemid {
|
||||
continue
|
||||
}
|
||||
return itemCfg, idx, true
|
||||
}
|
||||
|
||||
return itemCfg, 0, false
|
||||
}
|
||||
|
||||
func handleNewSeIdxPetItem(itemid uint32, onpet *model.PetInfo) errorcode.ErrorCode {
|
||||
itemCfg, newSeIdx, ok := resolvePetItemNewSeIdx(itemid)
|
||||
if ok && newSeIdx == 0 {
|
||||
if itemCfg.MaxHPUp > 0 {
|
||||
if !onpet.AddMaxHPUpEffect(itemid, itemCfg.MaxHPUp) {
|
||||
return errorcode.ErrorCodes.ErrCannotInjectPillAgain
|
||||
}
|
||||
return 0
|
||||
}
|
||||
return errorcode.ErrorCodes.ErrItemUnusable
|
||||
}
|
||||
if !ok {
|
||||
return errorcode.ErrorCodes.ErrItemUnusable
|
||||
}
|
||||
|
||||
effectCfg, ok := xmlres.EffectMAP[newSeIdx]
|
||||
if !ok {
|
||||
return errorcode.ErrorCodes.ErrSystemError
|
||||
}
|
||||
|
||||
effectStatus := byte(gconv.Int(effectCfg.Stat))
|
||||
effectIdx := uint16(newSeIdx)
|
||||
leftCount := 1
|
||||
if effectCfg.Times != nil && *effectCfg.Times != "" {
|
||||
leftCount = gconv.Int(*effectCfg.Times)
|
||||
if leftCount <= 0 {
|
||||
leftCount = 1
|
||||
}
|
||||
}
|
||||
|
||||
limitedCount := 0
|
||||
for _, eff := range onpet.EffectInfo {
|
||||
if eff.Idx == effectIdx {
|
||||
return errorcode.ErrorCodes.ErrCannotInjectPillAgain
|
||||
}
|
||||
if eff.Status == 2 {
|
||||
limitedCount++
|
||||
}
|
||||
}
|
||||
if effectStatus == 2 && limitedCount >= 2 {
|
||||
return errorcode.ErrorCodes.ErrTooManyEnergyOrbs
|
||||
}
|
||||
|
||||
onpet.EffectInfo = append(onpet.EffectInfo, model.PetEffectInfo{
|
||||
ItemID: itemid,
|
||||
Idx: effectIdx,
|
||||
Status: effectStatus,
|
||||
LeftCount: byte(leftCount),
|
||||
EID: uint16(gconv.Int(effectCfg.Eid)),
|
||||
Args: effectCfg.ArgsS,
|
||||
})
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r *PetItemHandlerRegistry) Handle(itemID uint32, onpet *model.PetInfo) errorcode.ErrorCode {
|
||||
handler := r.GetHandler(itemID)
|
||||
if handler != nil {
|
||||
if handler(itemID, onpet) {
|
||||
return 0
|
||||
}
|
||||
return errorcode.ErrorCodes.ErrItemUnusable
|
||||
}
|
||||
return handleNewSeIdxPetItem(itemID, onpet)
|
||||
}
|
||||
|
||||
// -------------------------- 6. 初始化注册器(注册所有处理器) --------------------------
|
||||
func init() {
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ package player
|
||||
|
||||
import (
|
||||
"blazing/common/utils"
|
||||
"blazing/modules/config/model"
|
||||
configmodel "blazing/modules/config/model"
|
||||
playermodel "blazing/modules/player/model"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -10,15 +11,17 @@ import (
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func (p *Player) IsMatch(t model.Event) bool {
|
||||
_, ok := lo.Find(t.Weather, func(item int32) bool {
|
||||
return item == int32(p.GetSpace().MapBossSInfo.Wer)
|
||||
})
|
||||
if !ok {
|
||||
// 不在同一天气下
|
||||
return false
|
||||
|
||||
func (p *Player) IsMatch(t configmodel.Event) bool {
|
||||
if len(t.Weather) > 0 {
|
||||
_, ok := lo.Find(t.Weather, func(item int32) bool {
|
||||
return item == int32(p.GetSpace().MapBossSInfo.Wer)
|
||||
})
|
||||
if !ok {
|
||||
// 不在同一天气下
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if t.StartTime != "" && t.EndTime != "" {
|
||||
ok, _ := utils.IsCurrentTimeInRange(t.StartTime, t.EndTime)
|
||||
if !ok {
|
||||
@@ -26,8 +29,74 @@ func (p *Player) IsMatch(t model.Event) bool {
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
if len(t.Week) > 0 {
|
||||
week := int32(time.Now().Weekday())
|
||||
if week == 0 {
|
||||
week = 7
|
||||
}
|
||||
|
||||
_, ok := lo.Find(t.Week, func(item int32) bool {
|
||||
return item == week
|
||||
})
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(t.Sprites) > 0 && !matchPetIDInList(t.Sprites, p.Info.PetList, p.Info.BackupPetList) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(t.FirstSprites) > 0 {
|
||||
if len(p.Info.PetList) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
firstPetID := int32(p.Info.PetList[0].ID)
|
||||
_, ok := lo.Find(t.FirstSprites, func(item int32) bool {
|
||||
return item == firstPetID
|
||||
})
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if len(t.MustTask) > 0 {
|
||||
for _, taskID := range t.MustTask {
|
||||
if p.Info.GetTask(int(taskID)) != playermodel.Completed {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(t.MustItem) > 0 {
|
||||
if p.Service == nil || p.Service.Item == nil {
|
||||
return false
|
||||
}
|
||||
for _, itemID := range t.MustItem {
|
||||
if p.Service.Item.CheakItem(uint32(itemID)) <= 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func matchPetIDInList(targetIDs []int32, petLists ...[]playermodel.PetInfo) bool {
|
||||
for _, pets := range petLists {
|
||||
for _, pet := range pets {
|
||||
petID := int32(pet.ID)
|
||||
_, ok := lo.Find(targetIDs, func(item int32) bool {
|
||||
return item == petID
|
||||
})
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 应该根据怪物信息决定后端生成
|
||||
|
||||
@@ -4,5 +4,5 @@ type AI_player struct {
|
||||
baseplayer
|
||||
|
||||
CanCapture int
|
||||
|
||||
BossScript string
|
||||
}
|
||||
|
||||
@@ -44,6 +44,27 @@ func (p *baseplayer) GetPetInfo(limitlevel uint32) []model.PetInfo {
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *baseplayer) AddBattleProp(index int, level int8) {
|
||||
if p == nil || index < 0 || index >= len(p.Prop) || level == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
p.Prop[index] += level
|
||||
if p.Prop[index] > 6 {
|
||||
p.Prop[index] = 6
|
||||
}
|
||||
if p.Prop[index] < -6 {
|
||||
p.Prop[index] = -6
|
||||
}
|
||||
}
|
||||
|
||||
func (p *baseplayer) ApplyBattleProps(target *model.AttackValue) {
|
||||
if p == nil || target == nil {
|
||||
return
|
||||
}
|
||||
target.Prop = p.Prop
|
||||
}
|
||||
func (f *baseplayer) InvitePlayer(ff common.PlayerI) {
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"context"
|
||||
|
||||
@@ -22,6 +23,11 @@ import (
|
||||
"github.com/panjf2000/gnet/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
minPacketLen = 17
|
||||
maxPacketLen = 10 * 1024
|
||||
)
|
||||
|
||||
// getUnderlyingValue 递归解析reflect.Value,解包指针、interface{}到底层具体类型
|
||||
func getUnderlyingValue(val reflect.Value) (reflect.Value, error) {
|
||||
for {
|
||||
@@ -47,6 +53,44 @@ func getUnderlyingValue(val reflect.Value) (reflect.Value, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func setFieldByIndex(root reflect.Value, index []int, value reflect.Value) bool {
|
||||
current := root
|
||||
for pos, idx := range index {
|
||||
if current.Kind() == reflect.Ptr {
|
||||
if current.IsNil() {
|
||||
current.Set(reflect.New(current.Type().Elem()))
|
||||
}
|
||||
current = current.Elem()
|
||||
}
|
||||
|
||||
if current.Kind() != reflect.Struct || idx < 0 || idx >= current.NumField() {
|
||||
return false
|
||||
}
|
||||
|
||||
field := current.Field(idx)
|
||||
if pos == len(index)-1 {
|
||||
if !field.CanSet() {
|
||||
return false
|
||||
}
|
||||
if value.Type().AssignableTo(field.Type()) {
|
||||
field.Set(value)
|
||||
return true
|
||||
}
|
||||
if field.Kind() == reflect.Ptr && value.Type().AssignableTo(field.Type().Elem()) {
|
||||
ptr := reflect.New(field.Type().Elem())
|
||||
ptr.Elem().Set(value)
|
||||
field.Set(ptr)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
current = field
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// XORDecryptU 原地执行异或解密,避免额外分配和拷贝。
|
||||
func XORDecryptU(encryptedData []byte, key uint32) []byte {
|
||||
if len(encryptedData) == 0 {
|
||||
@@ -102,6 +146,16 @@ func putPacketData(buf []byte) {
|
||||
}
|
||||
|
||||
func (h *ClientData) PushEvent(v []byte, submit func(task func()) error) {
|
||||
if h == nil || h.IsClosed() {
|
||||
return
|
||||
}
|
||||
if len(v) < minPacketLen || len(v) > maxPacketLen {
|
||||
return
|
||||
}
|
||||
if binary.BigEndian.Uint32(v[0:4]) != uint32(len(v)) {
|
||||
return
|
||||
}
|
||||
|
||||
var header common.TomeeHeader
|
||||
header.Len = binary.BigEndian.Uint32(v[0:4])
|
||||
header.CMD = binary.BigEndian.Uint32(v[5:9])
|
||||
@@ -111,9 +165,18 @@ func (h *ClientData) PushEvent(v []byte, submit func(task func()) error) {
|
||||
copy(header.Data, v[17:])
|
||||
}
|
||||
|
||||
_ = submit(func() {
|
||||
h.LF.Producer().Write(header)
|
||||
err := submit(func() {
|
||||
if h.IsClosed() || h.LF == nil || !h.LF.Running() {
|
||||
putPacketData(header.Data)
|
||||
return
|
||||
}
|
||||
if err := h.LF.Producer().Write(header); err != nil {
|
||||
putPacketData(header.Data)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
putPacketData(header.Data)
|
||||
}
|
||||
}
|
||||
|
||||
// 重写
|
||||
@@ -168,45 +231,38 @@ func (h *ClientData) OnEvent(data common.TomeeHeader) {
|
||||
return //TODO 待实现cmd未注册
|
||||
}
|
||||
|
||||
params := []reflect.Value{}
|
||||
var ptrValue reflect.Value
|
||||
if cmdlister.NewReqValue != nil {
|
||||
ptrValue = cmdlister.NewReqValue()
|
||||
} else {
|
||||
ptrValue = reflect.New(cmdlister.Req)
|
||||
}
|
||||
|
||||
//funct := cmdlister.Type().NumIn()
|
||||
|
||||
// 如果需要可设置的变量(用于修改值),创建指针并解引用
|
||||
ptrValue := reflect.New(cmdlister.Req)
|
||||
|
||||
// fmt.Println(tt1)
|
||||
if data.Res != nil {
|
||||
tt1 := ptrValue.Elem().Addr().Interface()
|
||||
err := struc.Unpack(bytes.NewBuffer(data.Res), tt1)
|
||||
err := struc.Unpack(bytes.NewBuffer(data.Res), ptrValue.Interface())
|
||||
|
||||
if err != nil {
|
||||
|
||||
cool.Logger.Error(context.Background(), data.UserID, data.CMD, "解包失败,", err, hex.EncodeToString(data.Res))
|
||||
//fmt.Println(data.UserID, data.CMD, "解包失败,", hex.EncodeToString(data.Data))
|
||||
data.Result = uint32(errorcode.ErrorCodes.ErrSystemProcessingError)
|
||||
h.SendPack(data.Pack(nil))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ptrValue1 := ptrValue.Elem().Addr()
|
||||
// 设置 Name 字段
|
||||
nameField := ptrValue.Elem().Field(0) //首个为header
|
||||
nameField.Set(reflect.ValueOf(data))
|
||||
|
||||
if data.CMD > 1001 { //if cmdlister.Type().In(1) == reflect.TypeOf(&Player{}) {
|
||||
//t := GetPlayer(c, data.UserID)
|
||||
|
||||
// fmt.Println(data.CMD, "接收 变量的地址 ", &t.Info, t.Info.UserID)
|
||||
|
||||
params = append(params, ptrValue1, reflect.ValueOf(h.Player))
|
||||
} else {
|
||||
|
||||
params = append(params, ptrValue1, reflect.ValueOf(h.Conn))
|
||||
if !setFieldByIndex(ptrValue.Elem(), cmdlister.HeaderFieldIndex, reflect.ValueOf(data)) {
|
||||
cool.Logger.Warning(context.Background(), data.UserID, data.CMD, "设置请求头失败")
|
||||
return
|
||||
}
|
||||
|
||||
ret := cmdlister.Func.Call(params)
|
||||
var params [2]reflect.Value
|
||||
params[0] = ptrValue
|
||||
if cmdlister.UseConn {
|
||||
params[1] = reflect.ValueOf(h.Conn)
|
||||
} else {
|
||||
params[1] = reflect.ValueOf(h.Player)
|
||||
}
|
||||
|
||||
ret := cmdlister.Func.Call(params[:])
|
||||
|
||||
if len(ret) <= 0 { //如果判断没有参数,那就说明这个包没有返回参数
|
||||
return
|
||||
@@ -235,12 +291,34 @@ func (h *ClientData) OnEvent(data common.TomeeHeader) {
|
||||
}
|
||||
|
||||
type ClientData struct {
|
||||
IsCrossDomain sync.Once //是否跨域过
|
||||
Player *Player //客户实体
|
||||
ERROR_CONNUT int
|
||||
Wsmsg *WsCodec
|
||||
Conn gnet.Conn
|
||||
LF *lockfree.Lockfree[common.TomeeHeader]
|
||||
Player *Player //客户实体
|
||||
ERROR_CONNUT int
|
||||
Wsmsg *WsCodec
|
||||
Conn gnet.Conn
|
||||
LF *lockfree.Lockfree[common.TomeeHeader]
|
||||
closed int32
|
||||
crossDomainChecked uint32
|
||||
}
|
||||
|
||||
func (p *ClientData) IsClosed() bool {
|
||||
return atomic.LoadInt32(&p.closed) == 1
|
||||
}
|
||||
|
||||
func (p *ClientData) Close() {
|
||||
if !atomic.CompareAndSwapInt32(&p.closed, 0, 1) {
|
||||
return
|
||||
}
|
||||
if p.LF != nil && p.LF.Running() {
|
||||
_ = p.LF.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ClientData) IsCrossDomainChecked() bool {
|
||||
return atomic.LoadUint32(&p.crossDomainChecked) == 1
|
||||
}
|
||||
|
||||
func (p *ClientData) MarkCrossDomainChecked() {
|
||||
atomic.StoreUint32(&p.crossDomainChecked, 1)
|
||||
}
|
||||
|
||||
func (p *ClientData) GetPlayer(userid uint32) *Player { //TODO 这里待优化,可能存在内存泄漏问题
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package player
|
||||
|
||||
import "blazing/common/data/share"
|
||||
|
||||
func KickPlayer(userid uint32) error { //踢出玩家
|
||||
//TODO 返回错误码
|
||||
//var player *entity.Player
|
||||
if player1, ok := Mainplayer.Load(userid); ok {
|
||||
player1.Player.Kick(false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//return player
|
||||
// 已不在本服在线列表,视为离线并清理僵尸在线标记
|
||||
_ = share.ShareManager.DeleteUserOnline(userid)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package player
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
@@ -11,12 +12,18 @@ import (
|
||||
"github.com/panjf2000/gnet/v2/pkg/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
minTCPPacketLen = 17
|
||||
maxTCPPacketLen = 10 * 1024
|
||||
tomeeVersion = 49
|
||||
)
|
||||
|
||||
type WsCodec struct {
|
||||
Tcp bool
|
||||
Upgraded bool // 链接是否升级
|
||||
Buf bytes.Buffer // 从实际socket中读取到的数据缓存
|
||||
wsMsgBuf wsMessageBuf // ws 消息缓存
|
||||
//Isinitws bool
|
||||
Tcp bool
|
||||
Upgraded bool // 链接是否升级
|
||||
Buf bytes.Buffer // 从实际socket中读取到的数据缓存
|
||||
wsMsgBuf wsMessageBuf // ws 消息缓存
|
||||
bufferedInbound int // 已镜像到 Buf 中的 inbound 字节数
|
||||
}
|
||||
|
||||
type wsMessageBuf struct {
|
||||
@@ -24,92 +31,115 @@ type wsMessageBuf struct {
|
||||
cachedBuf bytes.Buffer
|
||||
}
|
||||
|
||||
type UpgradeState uint8
|
||||
|
||||
const (
|
||||
UpgradeNeedMoreData UpgradeState = iota
|
||||
UpgradeUseTCP
|
||||
UpgradeUseWS
|
||||
)
|
||||
|
||||
type readWrite struct {
|
||||
io.Reader
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func CompareLeftBytes(array1, array2 []byte, leftBytesCount int) bool {
|
||||
// 检查切片长度是否足够比较左边的字节
|
||||
if len(array1) < leftBytesCount || len(array2) < leftBytesCount {
|
||||
return false
|
||||
}
|
||||
|
||||
// 提取左边的字节切片
|
||||
left1 := array1[:leftBytesCount]
|
||||
left2 := array2[:leftBytesCount]
|
||||
|
||||
// 比较左边的字节切片
|
||||
for i := 0; i < leftBytesCount; i++ {
|
||||
if left1[i] != left2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
func (w *WsCodec) Upgrade(c gnet.Conn) (ok bool, action gnet.Action) {
|
||||
func (w *WsCodec) Upgrade(c gnet.Conn) (state UpgradeState, action gnet.Action) {
|
||||
if w.Upgraded {
|
||||
ok = true
|
||||
state = UpgradeUseWS
|
||||
return
|
||||
}
|
||||
|
||||
if w.Tcp {
|
||||
ok = false
|
||||
state = UpgradeUseTCP
|
||||
return
|
||||
}
|
||||
buf := &w.Buf
|
||||
if CompareLeftBytes(buf.Bytes(), []byte{0, 0}, 2) {
|
||||
w.Tcp = true
|
||||
return
|
||||
}
|
||||
tmpReader := bytes.NewReader(buf.Bytes())
|
||||
oldLen := tmpReader.Len()
|
||||
//logging.Infof("do Upgrade")
|
||||
|
||||
buf := w.Buf.Bytes()
|
||||
if looksLikeTCPPacket(buf) {
|
||||
w.SwitchToTCP()
|
||||
state = UpgradeUseTCP
|
||||
return
|
||||
}
|
||||
if len(buf) == 0 {
|
||||
state = UpgradeNeedMoreData
|
||||
return
|
||||
}
|
||||
|
||||
tmpReader := bytes.NewReader(buf)
|
||||
oldLen := tmpReader.Len()
|
||||
hs, err := ws.Upgrade(readWrite{tmpReader, c})
|
||||
skipN := oldLen - tmpReader.Len()
|
||||
if err != nil {
|
||||
if err == io.EOF || errors.Is(err, io.ErrUnexpectedEOF) { //数据不完整,不跳过 buf 中的 skipN 字节(此时 buf 中存放的仅是部分 "handshake data" bytes),下次再尝试读取
|
||||
if err == io.EOF || errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
state = UpgradeNeedMoreData
|
||||
return
|
||||
}
|
||||
buf.Next(skipN)
|
||||
w.Buf.Next(skipN)
|
||||
logging.Errorf("conn[%v] [err=%v]", c.RemoteAddr().String(), err.Error())
|
||||
action = gnet.Close
|
||||
//ok = true
|
||||
//w.Tcp = true
|
||||
return
|
||||
}
|
||||
buf.Next(skipN)
|
||||
logging.Infof("conn[%v] upgrade websocket protocol! Handshake: %v", c.RemoteAddr().String(), hs)
|
||||
|
||||
ok = true
|
||||
w.Buf.Next(skipN)
|
||||
logging.Infof("conn[%v] upgrade websocket protocol! Handshake: %v", c.RemoteAddr().String(), hs)
|
||||
w.Upgraded = true
|
||||
state = UpgradeUseWS
|
||||
return
|
||||
}
|
||||
|
||||
func looksLikeTCPPacket(buf []byte) bool {
|
||||
if len(buf) < 4 {
|
||||
return false
|
||||
}
|
||||
packetLen := binary.BigEndian.Uint32(buf[:4])
|
||||
if packetLen < minTCPPacketLen || packetLen > maxTCPPacketLen {
|
||||
return false
|
||||
}
|
||||
if len(buf) >= 5 && buf[4] != tomeeVersion {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *WsCodec) ReadBufferBytes(c gnet.Conn) (gnet.Action, int) {
|
||||
size := c.InboundBuffered()
|
||||
//buf := make([]byte, size)
|
||||
if size < w.bufferedInbound {
|
||||
w.bufferedInbound = 0
|
||||
}
|
||||
if size == w.bufferedInbound {
|
||||
return gnet.None, size
|
||||
}
|
||||
|
||||
read, err := c.Peek(size)
|
||||
if err != nil {
|
||||
logging.Errorf("read err! %v", err)
|
||||
return gnet.Close, 0
|
||||
}
|
||||
// if read < size {
|
||||
// logging.Errorf("read bytes len err! size: %d read: %d", size, read)
|
||||
// return gnet.Close
|
||||
// }
|
||||
w.Buf.Write(read)
|
||||
w.Buf.Write(read[w.bufferedInbound:])
|
||||
w.bufferedInbound = size
|
||||
return gnet.None, size
|
||||
}
|
||||
|
||||
func (w *WsCodec) ResetInboundMirror() {
|
||||
w.bufferedInbound = 0
|
||||
}
|
||||
|
||||
func (w *WsCodec) SwitchToTCP() {
|
||||
w.Tcp = true
|
||||
w.Upgraded = false
|
||||
w.bufferedInbound = 0
|
||||
w.Buf.Reset()
|
||||
w.wsMsgBuf.curHeader = nil
|
||||
w.wsMsgBuf.cachedBuf.Reset()
|
||||
}
|
||||
|
||||
func (w *WsCodec) Decode(c gnet.Conn) (outs []wsutil.Message, err error) {
|
||||
// fmt.Println("do Decode")
|
||||
messages, err := w.readWsMessages()
|
||||
if err != nil {
|
||||
logging.Errorf("Error reading message! %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if len(messages) <= 0 { //没有读到完整数据 不处理
|
||||
if len(messages) <= 0 {
|
||||
return
|
||||
}
|
||||
for _, message := range messages {
|
||||
@@ -131,9 +161,8 @@ func (w *WsCodec) readWsMessages() (messages []wsutil.Message, err error) {
|
||||
msgBuf := &w.wsMsgBuf
|
||||
in := &w.Buf
|
||||
for {
|
||||
// 从 in 中读出 header,并将 header bytes 写入 msgBuf.cachedBuf
|
||||
if msgBuf.curHeader == nil {
|
||||
if in.Len() < ws.MinHeaderSize { //头长度至少是2
|
||||
if in.Len() < ws.MinHeaderSize {
|
||||
return
|
||||
}
|
||||
var head ws.Header
|
||||
@@ -142,13 +171,13 @@ func (w *WsCodec) readWsMessages() (messages []wsutil.Message, err error) {
|
||||
if err != nil {
|
||||
return messages, err
|
||||
}
|
||||
} else { //有可能不完整,构建新的 reader 读取 head,读取成功才实际对 in 进行读操作
|
||||
} else {
|
||||
tmpReader := bytes.NewReader(in.Bytes())
|
||||
oldLen := tmpReader.Len()
|
||||
head, err = ws.ReadHeader(tmpReader)
|
||||
skipN := oldLen - tmpReader.Len()
|
||||
if err != nil {
|
||||
if err == io.EOF || errors.Is(err, io.ErrUnexpectedEOF) { //数据不完整
|
||||
if err == io.EOF || errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
return messages, nil
|
||||
}
|
||||
in.Next(skipN)
|
||||
@@ -163,21 +192,19 @@ func (w *WsCodec) readWsMessages() (messages []wsutil.Message, err error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
dataLen := (int)(msgBuf.curHeader.Length)
|
||||
// 从 in 中读出 data,并将 data bytes 写入 msgBuf.cachedBuf
|
||||
if dataLen > 0 {
|
||||
if in.Len() < dataLen { //数据不完整
|
||||
|
||||
dataLen := int(msgBuf.curHeader.Length)
|
||||
if dataLen > 0 {
|
||||
if in.Len() < dataLen {
|
||||
logging.Infof("incomplete data")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = io.CopyN(&msgBuf.cachedBuf, in, int64(dataLen))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if msgBuf.curHeader.Fin { //当前 header 已经是一个完整消息
|
||||
if msgBuf.curHeader.Fin {
|
||||
messages, err = wsutil.ReadClientMessage(&msgBuf.cachedBuf, messages)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -45,6 +45,7 @@ type Space struct {
|
||||
IsTime bool
|
||||
DropItemIds []uint32
|
||||
PitS *csmap.CsMap[int, []model.MapPit]
|
||||
MapNodeS *csmap.CsMap[uint32, *model.MapNode]
|
||||
}
|
||||
|
||||
func NewSpace() *Space {
|
||||
@@ -52,6 +53,7 @@ func NewSpace() *Space {
|
||||
ret := &Space{
|
||||
User: csmap.New[uint32, common.PlayerI](),
|
||||
UserInfo: csmap.New[uint32, info.SimpleInfo](),
|
||||
MapNodeS: csmap.New[uint32, *model.MapNode](),
|
||||
}
|
||||
|
||||
return ret
|
||||
@@ -185,12 +187,20 @@ func (ret *Space) init() {
|
||||
}
|
||||
ret.MapBossSInfo = info.MapModelBroadcastInfo{}
|
||||
ret.MapBossSInfo.INFO = make([]info.MapModelBroadcastEntry, 0)
|
||||
|
||||
mapNodes := service.NewMapNodeService().GetData(ret.ID)
|
||||
for i := range mapNodes {
|
||||
ret.MapNodeS.Store(mapNodes[i].NodeID, &mapNodes[i])
|
||||
}
|
||||
if len(r.WeatherType) > 1 {
|
||||
ret.WeatherType = r.WeatherType
|
||||
|
||||
cool.Cron.CustomFunc(ret, ret.GenWer)
|
||||
}
|
||||
for _, v := range service.NewMapNodeService().GetDataB(ret.ID) {
|
||||
for _, v := range mapNodes {
|
||||
if v.IsBroadcast == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
r := service.NewMapmodelService().GetDataByModelId(v.IsBroadcast)
|
||||
if r == nil {
|
||||
@@ -220,15 +230,29 @@ func (ret *Space) init() {
|
||||
}
|
||||
|
||||
}
|
||||
func (p *Space) IsMatch(t model.Event) bool {
|
||||
_, ok := lo.Find(t.Weather, func(item int32) bool {
|
||||
return item == int32(p.MapBossSInfo.Wer)
|
||||
})
|
||||
if !ok {
|
||||
|
||||
return false
|
||||
|
||||
func (p *Space) GetMatchedMapNode(nodeID uint32) *model.MapNode {
|
||||
if p == nil || p.MapNodeS == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
mapNode, ok := p.MapNodeS.Load(nodeID)
|
||||
if !ok || mapNode == nil || mapNode.Event == nil || !p.IsMatch(*mapNode.Event) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return mapNode
|
||||
}
|
||||
|
||||
func (p *Space) IsMatch(t model.Event) bool {
|
||||
if len(t.Weather) > 0 {
|
||||
_, ok := lo.Find(t.Weather, func(item int32) bool {
|
||||
return item == int32(p.MapBossSInfo.Wer)
|
||||
})
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if t.StartTime != "" && t.EndTime != "" {
|
||||
ok, _ := utils.IsCurrentTimeInRange(t.StartTime, t.EndTime)
|
||||
if !ok {
|
||||
@@ -236,6 +260,20 @@ func (p *Space) IsMatch(t model.Event) bool {
|
||||
}
|
||||
}
|
||||
|
||||
if len(t.Week) > 0 {
|
||||
week := int32(time.Now().Weekday())
|
||||
if week == 0 {
|
||||
week = 7
|
||||
}
|
||||
|
||||
_, ok := lo.Find(t.Week, func(item int32) bool {
|
||||
return item == week
|
||||
})
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user