diff --git a/common/utils/emap.go b/common/utils/emap.go index acbd83a0..b90b136e 100644 --- a/common/utils/emap.go +++ b/common/utils/emap.go @@ -1,221 +1,220 @@ package utils import ( - "fmt" - "hash/maphash" + "encoding/json" + "sort" "sync" ) -// MapEntry 表示有序Map中的单个键值对条目,维护双向链表和哈希冲突链表指针 -type MapEntry[K comparable, V any] struct { - key K - value V - - // 双向链表指针:维护键值对的插入顺序 - prev, next *MapEntry[K, V] - // 哈希冲突链表指针:处理哈希表中相同哈希值的键 - hashNext *MapEntry[K, V] - // deleted 标记条目是否被逻辑删除(避免直接删除链表节点导致遍历断裂) - deleted bool +// OrderMap 是一个支持排序、有序遍历、并发安全的泛型 map。 +// 遍历(Range / Iter / IterBuffered)为非阻塞:进入遍历时创建快照(短时间持锁), +// 然后在快照上遍历(不持锁),因此回调可以做耗时操作而不阻塞写操作。 +type OrderMap[K comparable, V any] struct { + mu sync.RWMutex + data map[K]V + keys []K + less func(a, b K) bool // key 排序规则;为 nil 时按插入顺序 } -// OrderedMap 泛型有序Map实现,支持按插入顺序遍历、并发安全,且修复Range期间添加阻塞问题 -type OrderedMap[K comparable, V any] struct { - mu sync.RWMutex // 读写锁:保证并发安全,读多写少场景更高效 - hash *maphash.Hash // 哈希计算器:为键生成哈希值用于快速查找 - hashTable map[uint64]*MapEntry[K, V] // 哈希表:存储哈希值到对应冲突链表头的映射 - first *MapEntry[K, V] // 双向链表头:指向第一个插入的有效条目 - last *MapEntry[K, V] // 双向链表尾:指向最后一个插入的有效条目 - size int // 有效条目数量:排除已逻辑删除的条目 -} - -// NewOrderedMap 创建一个新的泛型有序Map实例 -func NewOrderedMap[K comparable, V any]() *OrderedMap[K, V] { - return &OrderedMap[K, V]{ - hash: &maphash.Hash{}, - hashTable: make(map[uint64]*MapEntry[K, V]), +// NewOrderedMap 创建一个 OrderMap;less 为 nil 时按插入顺序,否则会在写入后按 less 排序 keys。 +func NewOrderedMap[K comparable, V any](less func(a, b K) bool) *OrderMap[K, V] { + return &OrderMap[K, V]{ + data: make(map[K]V), + keys: make([]K, 0), + less: less, } } -// hashKey 为指定键生成哈希值(内部方法,需在外层加锁保证并发安全) -// 注:复杂类型需自定义序列化逻辑,此处简化为通过fmt.Sprintf转为字符串后计算哈希 -func (m *OrderedMap[K, V]) hashKey(key K) uint64 { - m.hash.Reset() - m.hash.WriteString(fmt.Sprintf("%v", key)) - return m.hash.Sum64() +// Set 设置键值(存在则覆盖,不存在则添加)。若提供了 less,则会在插入后排序 keys。 +func (om *OrderMap[K, V]) Set(k K, v V) { + om.mu.Lock() + if _, exist := om.data[k]; !exist { + om.keys = append(om.keys, k) + } + om.data[k] = v + // 若定义了 less,则保持 keys 有序 + if om.less != nil { + sort.Slice(om.keys, func(i, j int) bool { return om.less(om.keys[i], om.keys[j]) }) + } + om.mu.Unlock() } -// lookup 查找指定键对应的条目、其哈希值及哈希冲突链表中的前驱条目(内部方法,需外层加锁) -// 返回值:哈希值、目标条目(未找到或已删除则为nil)、哈希冲突链表中的前驱条目 -func (m *OrderedMap[K, V]) lookup(key K) (hash uint64, entry, prevHashEntry *MapEntry[K, V]) { - hash = m.hashKey(key) - // 遍历哈希冲突链表,查找未删除的目标键 - for entry = m.hashTable[hash]; entry != nil; prevHashEntry, entry = entry, entry.hashNext { - if !entry.deleted && entry.key == key { +// Get 获取一个键的值,返回值和是否存在(并发安全)。 +func (om *OrderMap[K, V]) Get(k K) (V, bool) { + om.mu.RLock() + v, ok := om.data[k] + om.mu.RUnlock() + return v, ok +} + +// Delete 删除一个键(并发安全)。 +func (om *OrderMap[K, V]) Delete(k K) { + om.mu.Lock() + if _, ok := om.data[k]; ok { + delete(om.data, k) + // 从 keys 切片中移除第一次出现的 k + for i, key := range om.keys { + if key == k { + om.keys = append(om.keys[:i], om.keys[i+1:]...) + break + } + } + } + om.mu.Unlock() +} + +// Keys 返回当前 keys 的有序副本(并发安全)。 +func (om *OrderMap[K, V]) Keys() []K { + om.mu.RLock() + keys := make([]K, len(om.keys)) + copy(keys, om.keys) + om.mu.RUnlock() + return keys +} + +// Values 返回当前 values 的有序副本(并发安全)。 +func (om *OrderMap[K, V]) Values() []V { + om.mu.RLock() + values := make([]V, 0, len(om.keys)) + for _, k := range om.keys { + values = append(values, om.data[k]) + } + om.mu.RUnlock() + return values +} + +// Len 返回 map 长度(并发安全)。 +func (om *OrderMap[K, V]) Len() int { + om.mu.RLock() + n := len(om.data) + om.mu.RUnlock() + return n +} + +// snapshot 复制一份 keys 列表和对应的数据(短时间持锁),返回 keys 及 map 的副本。 +// 注意:这是浅拷贝(V 为引用类型则仍为相同引用)。 +func (om *OrderMap[K, V]) snapshot() ([]K, map[K]V) { + om.mu.RLock() + keys := make([]K, len(om.keys)) + copy(keys, om.keys) + dataCopy := make(map[K]V, len(keys)) + for _, k := range keys { + dataCopy[k] = om.data[k] + } + om.mu.RUnlock() + return keys, dataCopy +} + +// Iter 返回一个无缓冲的 channel,用于在 for range 中遍历所有当前项。 +// Deprecated: 使用 IterBuffered() 性能更好。 +func (om *OrderMap[K, V]) Iter() <-chan Tuple[K, V] { + keys, dataCopy := om.snapshot() + ch := make(chan Tuple[K, V]) + go func() { + for _, k := range keys { + // 可能在原 map 中此 key 已被删除,但 snapshot 中仍存在 + if v, ok := dataCopy[k]; ok { + ch <- Tuple[K, V]{Key: k, Val: v} + } + } + close(ch) + }() + return ch +} + +// IterBuffered 返回一个带缓冲的 channel(缓冲大小为当前元素数),用于高效遍历。 +func (om *OrderMap[K, V]) IterBuffered() <-chan Tuple[K, V] { + keys, dataCopy := om.snapshot() + ch := make(chan Tuple[K, V], len(keys)) + go func() { + for _, k := range keys { + if v, ok := dataCopy[k]; ok { + ch <- Tuple[K, V]{Key: k, Val: v} + } + } + close(ch) + }() + return ch +} + +// Range 在非阻塞快照上按有序 keys 进行遍历。f 返回 false 可提前停止遍历。 +// Range 本身不会在回调期间持锁,因此回调可以做耗时操作。 +func (om *OrderMap[K, V]) Range(f func(K, V) bool) { + keys, dataCopy := om.snapshot() + for _, k := range keys { + v, ok := dataCopy[k] + if !ok { + continue + } + if !f(k, v) { break } } - return } -// Store 存储键值对:键存在则更新值,键不存在则新增(按插入顺序追加到链表末尾) -func (m *OrderedMap[K, V]) Store(key K, value V) { - m.mu.Lock() - defer m.mu.Unlock() - - hash, entry, prevHashEntry := m.lookup(key) - - // 情况1:键已存在(且未删除),直接更新值 - if entry != nil { - entry.value = value - // 若条目曾被逻辑删除,恢复为有效状态并更新计数 - if entry.deleted { - entry.deleted = false - m.size++ - } - return - } - - // 情况2:键不存在,创建新条目 - entry = &MapEntry[K, V]{ - key: key, - value: value, - } - - // 2.1 将新条目添加到哈希冲突链表(处理哈希碰撞) - if prevHashEntry == nil { - // 哈希桶为空,直接作为桶的头节点 - m.hashTable[hash] = entry - } else { - // 哈希桶已有节点,追加到冲突链表末尾 - prevHashEntry.hashNext = entry - } - - // 2.2 将新条目添加到双向链表末尾(保证插入顺序) - if m.last != nil { - // 链表已有节点,更新尾节点指针 - entry.prev = m.last - m.last.next = entry - } else { - // 链表为空,新条目同时作为头节点 - m.first = entry - } - // 更新尾节点为新条目 - m.last = entry - // 增加有效条目计数 - m.size++ +// Items 返回一份 map 的浅拷贝(并发安全)。注意:返回 map 的 key 顺序不可保证(map 本身无序) +// 若你需要有序的键值序列,使用 IterBuffered/Range/Keys。 +func (om *OrderMap[K, V]) Items() map[K]V { + _, dataCopy := om.snapshot() + // snapshot 已是 map 的拷贝,直接返回 + return dataCopy } -// Load 查找指定键的值:存在则返回值和true,不存在则返回对应类型零值和false -func (m *OrderedMap[K, V]) Load(key K) (value V, exists bool) { - m.mu.RLock() - defer m.mu.RUnlock() - - _, entry, _ := m.lookup(key) - // 仅当条目存在且未删除时,返回有效数据 - if entry != nil && !entry.deleted { - return entry.value, true - } - return value, false +// Clear 清空所有数据(并发安全)。 +func (om *OrderMap[K, V]) Clear() { + om.mu.Lock() + om.data = make(map[K]V) + om.keys = make([]K, 0) + om.mu.Unlock() } -// LoadOrStore 查找并返回键对应的值:存在则返回原值和true,不存在则存储并返回新值和false -func (m *OrderedMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) { - m.mu.Lock() - defer m.mu.Unlock() +// ---------------------------------------------------------------------------- +// JSON 支持(保持有序) +// ---------------------------------------------------------------------------- - _, entry, _ := m.lookup(key) - // 情况1:键已存在(未删除),返回原值 - if entry != nil && !entry.deleted { - return entry.value, true - } - - // 情况2:键不存在,执行存储逻辑 - hash := m.hashKey(key) - entry = &MapEntry[K, V]{ - key: key, - value: value, - } - - // 2.1 处理哈希冲突,添加到对应哈希桶 - if m.hashTable[hash] == nil { - m.hashTable[hash] = entry - } else { - // 遍历冲突链表到末尾,追加新条目 - lastHashEntry := m.hashTable[hash] - for lastHashEntry.hashNext != nil { - lastHashEntry = lastHashEntry.hashNext - } - lastHashEntry.hashNext = entry - } - - // 2.2 添加到双向链表末尾,保证插入顺序 - if m.last != nil { - entry.prev = m.last - m.last.next = entry - } else { - m.first = entry - } - m.last = entry - m.size++ - - // 返回存储的新值和“未加载(新存储)”标记 - return value, false +// jsonPair 用于 Marshal/Unmarshal 时保留顺序的中间结构:数组形式 [{key:..., value:...}, ...] +type jsonPair[K comparable, V any] struct { + Key K `json:"key"` + Value V `json:"value"` } -// Delete 逻辑删除指定键:仅标记deleted为true,不直接删除节点(避免遍历链表断裂) -func (m *OrderedMap[K, V]) Delete(key K) { - m.mu.Lock() - defer m.mu.Unlock() - - _, entry, _ := m.lookup(key) - // 仅当条目存在且未删除时,执行逻辑删除并更新计数 - if entry != nil && !entry.deleted { - entry.deleted = true - m.size-- - } -} - -// Range 按插入顺序遍历所有有效键值对:修复后支持遍历期间添加,新添加条目不影响当前遍历 -// 回调函数f返回false时,停止遍历 -func (m *OrderedMap[K, V]) Range(f func(key K, value V) bool) { - // 第一步:加读锁复制快照(仅持有极短时间,避免阻塞写操作) - m.mu.RLock() - // 预分配切片容量(等于有效条目数),减少动态扩容开销 - snapshot := make([]*MapEntry[K, V], 0, m.size) - current := m.first - // 遍历双向链表,收集所有未删除的有效条目 - for current != nil { - if !current.deleted { - snapshot = append(snapshot, current) - } - current = current.next - } - // 关键:复制完成后立即释放读锁,允许Store等写操作执行 - m.mu.RUnlock() - - // 第二步:遍历快照切片,执行用户回调(无锁状态,安全且高效) - for _, entry := range snapshot { - if !f(entry.key, entry.value) { - return // 回调返回false,终止遍历 +// MarshalJSON 将 OrderMap 以数组形式序列化,保持 keys 的顺序: +// [ +// +// {"key": , "value": }, +// {"key": , "value": }, +// ... +// +// ] +func (om *OrderMap[K, V]) MarshalJSON() ([]byte, error) { + keys, dataCopy := om.snapshot() + pairs := make([]jsonPair[K, V], 0, len(keys)) + for _, k := range keys { + if v, ok := dataCopy[k]; ok { + pairs = append(pairs, jsonPair[K, V]{Key: k, Value: v}) } } + return json.Marshal(pairs) } -// Len 返回当前Map中的有效条目数量(排除已逻辑删除的条目) -func (m *OrderedMap[K, V]) Len() int { - m.mu.RLock() - defer m.mu.RUnlock() - return m.size -} - -// Clear 清空Map所有内容:重置哈希表、双向链表和计数(物理清空,而非逻辑删除) -func (m *OrderedMap[K, V]) Clear() { - m.mu.Lock() - defer m.mu.Unlock() - - m.first = nil - m.last = nil - m.hashTable = make(map[uint64]*MapEntry[K, V]) - m.size = 0 +// UnmarshalJSON 从上述数组格式反序列化,按数组顺序恢复 keys 与数据。 +// 注意:若 Key 类型不是 JSON 可直接解码的类型,反序列化会失败。 +func (om *OrderMap[K, V]) UnmarshalJSON(b []byte) error { + var pairs []jsonPair[K, V] + if err := json.Unmarshal(b, &pairs); err != nil { + return err + } + om.mu.Lock() + defer om.mu.Unlock() + om.data = make(map[K]V, len(pairs)) + om.keys = make([]K, 0, len(pairs)) + for _, p := range pairs { + om.data[p.Key] = p.Value + om.keys = append(om.keys, p.Key) + } + // 若定义了 less,则保持 keys 排序 + if om.less != nil { + sort.Slice(om.keys, func(i, j int) bool { return om.less(om.keys[i], om.keys[j]) }) + } + return nil } diff --git a/logic/controller/fight.go b/logic/controller/fight.go index 0595a7fa..59526ff5 100644 --- a/logic/controller/fight.go +++ b/logic/controller/fight.go @@ -8,12 +8,20 @@ import ( "blazing/logic/service/fight/info" "blazing/logic/service/player" "blazing/modules/blazing/model" + + "github.com/gogf/gf/v2/util/gconv" ) // 挑战地图boss func (h Controller) PlayerFightBoss(data *fight.ChallengeBossInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) { + if c.FightC != nil { + return nil, errorcode.ErrorCodes.ErrOnlineOver6HoursCannotFight + } + var petid int var mo *model.PetInfo + moinfo := &model.PlayerInfo{} + if c.Info.MapID == 515 && data.BossId == 0 { //说明是新手,随机生成 switch c.Info.PetList[0].ID { @@ -33,14 +41,42 @@ func (h Controller) PlayerFightBoss(data *fight.ChallengeBossInboundInfo, c *pla 0, //野怪没特性 0, 2) + moinfo.Nick = xmlres.PetMAP[int(mo.ID)].DefName + moinfo.PetList = append(moinfo.PetList, *mo) + } else { + + mdata, ok := xmlres.MonsterMap[int(c.Info.MapID)] + if !ok { + return nil, errorcode.ErrorCodes.ErrPokemonNotExists + } + for _, bc := range mdata.Bosses { + if bc.Id == nil { + return nil, errorcode.ErrorCodes.ErrPokemonNotExists + + } + if uint32(*bc.Id) == data.BossId { + for _, bm := range bc.BossMon { + mo = c.GenPetInfo( + gconv.Int(bm.MonID), 24, //24个体 + -1, + 0, //野怪没特性 + 0, + 2) + + mo.Level = uint32(bm.Lv) + mo.CalculatePetPane() + mo.Hp = uint32(bm.Hp) + mo.MaxHp = uint32(bm.Hp) + moinfo.PetList = append(moinfo.PetList, *mo) + + } + moinfo.Nick = xmlres.PetMAP[int(mo.ID)].DefName + } + + } + } - if c.FightC != nil { - return nil, errorcode.ErrorCodes.ErrOnlineOver6HoursCannotFight - } - moinfo := &model.PlayerInfo{} - moinfo.Nick = xmlres.PetMAP[int(mo.ID)].DefName - moinfo.PetList = append(moinfo.PetList, *mo) ai := player.NewAI_player(moinfo) fight.NewFight(info.BattleMode.MULTI_MODE, info.BattleStatus.FIGHT_WITH_BOSS, c, ai) diff --git a/logic/service/fight/effect/effect_10-16_94_99_114.go b/logic/service/fight/effect/effect_10-16_94_99_114.go index 9aa04bc1..c4fca3bd 100644 --- a/logic/service/fight/effect/effect_10-16_94_99_114.go +++ b/logic/service/fight/effect/effect_10-16_94_99_114.go @@ -59,7 +59,7 @@ func (e *Effect10) OnHit(opp *input.Input, skill *info.SkillEntity) { eff := input.Geteffect(input.EffectType.Status, int(e.Status)) if eff.ID != 0 { - eff.Effect.Duration(int(t1 + 1)) + eff.Effect.Duration(int(t1)) opp.AddEffect(eff) } diff --git a/logic/service/fight/fightc.go b/logic/service/fight/fightc.go index bf5ee4cc..26183f0c 100644 --- a/logic/service/fight/fightc.go +++ b/logic/service/fight/fightc.go @@ -253,7 +253,10 @@ func (f *FightC) battleLoop() { fmt.Println("战斗结束") break } + if f.closefight { //回合数超过250,战斗平局结束f.Round > 250 || + break + } if paction.GetPlayerID() != f.Our.Player.GetInfo().UserID && paction.GetPlayerID() != f.Opp.Player.GetInfo().UserID { continue } @@ -293,7 +296,7 @@ func (f *FightC) battleLoop() { } if _, exists := actions[f.Opp.Player.GetInfo().UserID]; !exists { - f.Over(f.Opp.Player, info.BattleOverReason.PlayerOVerTime) + f.Over(f.Our.Player, info.BattleOverReason.PlayerOVerTime) } } diff --git a/logic/service/fight/input/input.go b/logic/service/fight/input/input.go index 078a9336..bf63ca64 100644 --- a/logic/service/fight/input/input.go +++ b/logic/service/fight/input/input.go @@ -21,7 +21,7 @@ type Input struct { *info.AttackValue FightC common.FightI // info.BattleActionI - Effects *utils.OrderedMap[int, Effect] //effects 实际上全局就是effect无限回合 //effects容器 技能的 + Effects *utils.OrderMap[int, Effect] //effects 实际上全局就是effect无限回合 //effects容器 技能的 DamageZone struct { Damage decimal.Decimal //伤害 BeforeADD decimal.Decimal //攻击伤害 @@ -40,7 +40,7 @@ type Input struct { func NewInput(c common.FightI, p common.PlayerI) *Input { ret := &Input{FightC: c, Player: p} - ret.Effects = utils.NewOrderedMap[int, Effect]() + ret.Effects = utils.NewOrderedMap[int, Effect](nil) // t := Geteffect(EffectType.Damage, 0) // t.Effect.SetArgs(ret) @@ -58,7 +58,16 @@ func (i *Input) GetPetInfo() *info.BattlePetEntity { // 这个每回合都会调用 func (i *Input) InitAttackValue() { + var old *info.AttackValue + if i.AttackValue != nil { + old = i.AttackValue + + } i.AttackValue = info.NewAttackValue(i.Player.GetInfo().UserID) + if old != nil { + i.AttackValue.Prop = old.Prop + i.AttackValue.Status = old.Status + } } func (i *Input) GetPet(id uint32) (ii *info.BattlePetEntity, Reason info.ChangePetInfo) { diff --git a/logic/service/fight/input/node.go b/logic/service/fight/input/node.go index c00e684b..264807fe 100644 --- a/logic/service/fight/input/node.go +++ b/logic/service/fight/input/node.go @@ -70,7 +70,7 @@ func (c *Input) GetProp(id int, istue bool) int { } func (c *Input) GetEffect(etype EnumEffectType, id int) *EffectID { - rer, ok := c.Effects.Load(id + int(etype)) + rer, ok := c.Effects.Get(id + int(etype)) if ok { @@ -83,7 +83,7 @@ func (c *Input) GetEffect(etype EnumEffectType, id int) *EffectID { return &EffectID{} } func (c *Input) StatEffect_Exist(id int) bool { - rer, ok := c.Effects.Load(id + int(EffectType.Status)) + rer, ok := c.Effects.Get(id + int(EffectType.Status)) if ok && rer.Alive() { return true @@ -113,14 +113,14 @@ func (c *Input) AddEffect(e *EffectID) { //TODO 先激活 fmt.Println("产生回合数", e.ID, e.Effect.Duration()) // 如果已有同 ID 的效果,尝试叠加 - eff, ok := c.Effects.LoadOrStore(e.ID, e.Effect) - if ok { + eff, ok := c.Effects.Get(e.ID) + if !ok { + c.Effects.Set(e.ID, e.Effect) return - } if !eff.Alive() { //如果不存活 - c.Effects.Store(e.ID, e.Effect) + c.Effects.Set(e.ID, e.Effect) return } @@ -148,6 +148,7 @@ func (c *Input) AddEffect(e *EffectID) { // 返回值:所有 Effect 的方法返回值列表 func (c *Input) Exec(fn func(Effect) bool) bool { result := true + c.Effects.Range(func(key int, value Effect) bool { if value.Alive() { result1 := fn(value) diff --git a/logic/service/fight/node/Turn.go b/logic/service/fight/node/Turn.go index 35ad7ec7..577f3962 100644 --- a/logic/service/fight/node/Turn.go +++ b/logic/service/fight/node/Turn.go @@ -16,8 +16,10 @@ func (e *EffectNode) Turn_End(ctx input.Ctx) { if e.duration == 0 { // 保留 (负数表示永久) e.NotALive() + } else { + e.duration-- } - e.duration-- + } func (e *EffectNode) OnDefeat(*input.Input, *info.SkillEntity) bool {