```
feat: 更新战斗系统模型结构和Redis消息处理 - 引入gredis依赖用于Redis消息处理 - 将战斗相关的枚举和结构体从info包迁移到model包 - 更新战斗结束原因、攻击值等类型的引用路径 - 添加新的zset工具包到工作区 - 修改Redis消息处理逻辑以正确解析gredis.Message类型 - 在战斗控制器中统一使用model包下的类型定义
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gredis"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
@@ -158,17 +159,32 @@ func ListenFunc(ctx g.Ctx) {
|
||||
|
||||
// 处理消息(保留原有业务逻辑)
|
||||
if data != nil {
|
||||
dataMap := data.MapStrStr()
|
||||
if dataMap["Kind"] == "subscribe" {
|
||||
dataMap, ok := data.Interface().(*gredis.Message)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if dataMap["Channel"] == subscribeTopic {
|
||||
Logger.Debug(ctx, "执行函数", "payload", dataMap["Payload"])
|
||||
err := RunFunc(ctx, dataMap["Payload"])
|
||||
// if dataMap. == "subscribe" {
|
||||
// continue
|
||||
// }
|
||||
if dataMap.Channel == subscribeTopic {
|
||||
Logger.Debug(ctx, "执行函数", "payload", dataMap.Payload)
|
||||
err := RunFunc(ctx, dataMap.Payload)
|
||||
if err != nil {
|
||||
Logger.Error(ctx, "执行函数失败", "payload", dataMap["Payload"], "error", err)
|
||||
Logger.Error(ctx, "执行函数失败", "payload", dataMap.Payload, "error", err)
|
||||
}
|
||||
}
|
||||
if dataMap.Channel == "sun:join:2458" {
|
||||
|
||||
println("收到sun:join:2458", dataMap.Payload)
|
||||
//universalClient, _ := g.Redis("cool").Client().(goredis.UniversalClient)
|
||||
zs.Add(1001, User{ID: 1001, JoinTime: time.Now().Unix(), Score: 1000})
|
||||
if zs.Length() > 2 {
|
||||
zs.FindNext(func(i User) bool { return i.ID > 1000 })
|
||||
//找到上一个,如果区间分数少于一定,
|
||||
//直接进行匹配
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
25
common/cool/user.go
Normal file
25
common/cool/user.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package cool
|
||||
|
||||
import "github.com/liwnn/zset"
|
||||
|
||||
type User struct {
|
||||
JoinTime int64
|
||||
ID int
|
||||
Score int
|
||||
}
|
||||
|
||||
func (u User) Key() uint32 {
|
||||
return uint32(u.ID)
|
||||
}
|
||||
|
||||
// 如果分数不对的话,就按时间排序
|
||||
func (u User) Less(than User) bool {
|
||||
if u.Score == than.Score {
|
||||
return u.JoinTime < than.JoinTime
|
||||
}
|
||||
return u.Score < than.Score
|
||||
}
|
||||
|
||||
var zs = zset.New[uint32, User](func(a, b User) bool {
|
||||
return a.Less(b)
|
||||
})
|
||||
177
common/utils/zset/README.md
Normal file
177
common/utils/zset/README.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# ZSet
|
||||
This Go package provides an implementation of sorted set in redis.
|
||||
|
||||
## Usage (go < 1.18)
|
||||
All you have to do is to implement a comparison `function Less(Item) bool` and a `function Key() string` for your Item which will be store in the zset, here are some examples.
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/liwnn/zset"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
Score int
|
||||
}
|
||||
|
||||
func (u User) Key() string {
|
||||
return u.Name
|
||||
}
|
||||
|
||||
func (u User) Less(than zset.Item) bool {
|
||||
if u.Score == than.(User).Score {
|
||||
return u.Name < than.(User).Name
|
||||
}
|
||||
return u.Score < than.(User).Score
|
||||
}
|
||||
|
||||
func main() {
|
||||
zs := zset.New()
|
||||
|
||||
// Add
|
||||
zs.Add("Hurst", User{Name: "Hurst", Score: 88})
|
||||
zs.Add("Peek", User{Name: "Peek", Score: 100})
|
||||
zs.Add("Beaty", User{Name: "Beaty", Score: 66})
|
||||
|
||||
// Rank
|
||||
rank := zs.Rank("Hurst", true)
|
||||
fmt.Printf("Hurst's rank is %v\n", rank) // expected 2
|
||||
|
||||
// Range
|
||||
fmt.Println()
|
||||
fmt.Println("Range[0,3]:")
|
||||
zs.Range(0, 3, true, func(v zset.Item, rank int) bool {
|
||||
fmt.Printf("%v's rank is %v\n", v.(User).Key(), rank)
|
||||
return true
|
||||
})
|
||||
|
||||
// Range with Iterator
|
||||
fmt.Println()
|
||||
fmt.Println("Range[0,3] with Iterator:")
|
||||
for it := zs.RangeIterator(0, 3, true); it.Valid(); it.Next() {
|
||||
fmt.Printf("Ite: %v's rank is %v\n", it.Item().(User).Key(), it.Rank())
|
||||
}
|
||||
|
||||
// Range by score [88, 100]
|
||||
fmt.Println()
|
||||
fmt.Println("RangeByScore[88,100]:")
|
||||
zs.RangeByScore(func(i zset.Item) bool {
|
||||
return i.(User).Score >= 88
|
||||
}, func(i zset.Item) bool {
|
||||
return i.(User).Score <= 100
|
||||
}, true, func(i zset.Item, rank int) bool {
|
||||
fmt.Printf("%v's score[%v] rank is %v\n", i.(User).Key(), i.(User).Score, rank)
|
||||
return true
|
||||
})
|
||||
|
||||
// Remove
|
||||
zs.Remove("Peek")
|
||||
|
||||
// Rank
|
||||
fmt.Println()
|
||||
fmt.Println("After remove Peek:")
|
||||
rank = zs.Rank("Hurst", true)
|
||||
fmt.Printf("Hurst's rank is %v\n", rank) // expected 1
|
||||
}
|
||||
```
|
||||
Output:
|
||||
```
|
||||
Hurst's rank is 2
|
||||
|
||||
Range[0,3]:
|
||||
Peek's rank is 1
|
||||
Hurst's rank is 2
|
||||
Beaty's rank is 3
|
||||
|
||||
Range[0,3] with Iterator:
|
||||
Ite: Peek's rank is 1
|
||||
Ite: Hurst's rank is 2
|
||||
Ite: Beaty's rank is 3
|
||||
|
||||
RangeByScore[88,100]:
|
||||
Peek's score[100] rank is 1
|
||||
Hurst's score[88] rank is 2
|
||||
|
||||
After remove Peek:
|
||||
Hurst's rank is 1
|
||||
```
|
||||
## Usage (go >= 1.18)
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/liwnn/zset"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
Score int
|
||||
}
|
||||
|
||||
func (u User) Key() string {
|
||||
return u.Name
|
||||
}
|
||||
|
||||
func (u User) Less(than User) bool {
|
||||
if u.Score == than.Score {
|
||||
return u.Name < than.Name
|
||||
}
|
||||
return u.Score < than.Score
|
||||
}
|
||||
|
||||
func main() {
|
||||
zs := zset.New[string, User](func(a, b User) bool {
|
||||
return a.Less(b)
|
||||
})
|
||||
|
||||
// Add
|
||||
zs.Add("Hurst", User{Name: "Hurst", Score: 88})
|
||||
zs.Add("Peek", User{Name: "Peek", Score: 100})
|
||||
zs.Add("Beaty", User{Name: "Beaty", Score: 66})
|
||||
|
||||
// Rank
|
||||
rank := zs.Rank("Hurst", true)
|
||||
fmt.Printf("Hurst's rank is %v\n", rank) // expected 2
|
||||
|
||||
// Range
|
||||
fmt.Println()
|
||||
fmt.Println("Range[0,3]:")
|
||||
zs.Range(0, 3, true, func(v User, rank int) bool {
|
||||
fmt.Printf("%v's rank is %v\n", v.Key(), rank)
|
||||
return true
|
||||
})
|
||||
|
||||
// Range with Iterator
|
||||
fmt.Println()
|
||||
fmt.Println("Range[0,3] with Iterator:")
|
||||
for it := zs.RangeIterator(0, 3, true); it.Valid(); it.Next() {
|
||||
fmt.Printf("Ite: %v's rank is %v\n", it.Item().Key(), it.Rank())
|
||||
}
|
||||
|
||||
// Range by score [88, 100]
|
||||
fmt.Println()
|
||||
fmt.Println("RangeByScore[88,100]:")
|
||||
zs.RangeByScore(func(i User) bool {
|
||||
return i.Score >= 88
|
||||
}, func(i User) bool {
|
||||
return i.Score <= 100
|
||||
}, true, func(i User, rank int) bool {
|
||||
fmt.Printf("%v's score[%v] rank is %v\n", i.Key(), i.Score, rank)
|
||||
return true
|
||||
})
|
||||
|
||||
// Remove
|
||||
zs.Remove("Peek")
|
||||
|
||||
// Rank
|
||||
fmt.Println()
|
||||
fmt.Println("After remove Peek:")
|
||||
rank = zs.Rank("Hurst", true)
|
||||
fmt.Printf("Hurst's rank is %v\n", rank) // expected 1
|
||||
}
|
||||
```
|
||||
3
common/utils/zset/go.mod
Normal file
3
common/utils/zset/go.mod
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/liwnn/zset
|
||||
|
||||
go 1.18
|
||||
540
common/utils/zset/zset.go
Normal file
540
common/utils/zset/zset.go
Normal file
@@ -0,0 +1,540 @@
|
||||
//go:build !go1.18
|
||||
// +build !go1.18
|
||||
|
||||
// Package zset implements sorted set similar to redis zset.
|
||||
package zset
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultMaxLevel = 32 // (1/p)^MaxLevel >= maxNode
|
||||
DefaultP = 0.25 // SkipList P = 1/4
|
||||
|
||||
DefaultFreeListSize = 32
|
||||
)
|
||||
|
||||
// Item represents a single object in the set.
|
||||
type Item interface {
|
||||
// Less must provide a strict weak ordering
|
||||
Less(Item) bool
|
||||
}
|
||||
|
||||
// ItemIterator allows callers of Range* to iterate of the zset.
|
||||
// When this function returns false, iteration will stop.
|
||||
type ItemIterator func(i Item, rank int) bool
|
||||
|
||||
type skipListLevel struct {
|
||||
forward *node
|
||||
span int
|
||||
}
|
||||
|
||||
// node is an element of a skip list
|
||||
type node struct {
|
||||
item Item
|
||||
backward *node
|
||||
level []skipListLevel
|
||||
}
|
||||
|
||||
// FreeList represents a free list of set node.
|
||||
type FreeList struct {
|
||||
freelist []*node
|
||||
}
|
||||
|
||||
// NewFreeList creates a new free list.
|
||||
func NewFreeList(size int) *FreeList {
|
||||
return &FreeList{freelist: make([]*node, 0, size)}
|
||||
}
|
||||
|
||||
func (f *FreeList) newNode(lvl int) (n *node) {
|
||||
if len(f.freelist) == 0 {
|
||||
n = new(node)
|
||||
n.level = make([]skipListLevel, lvl)
|
||||
return
|
||||
}
|
||||
index := len(f.freelist) - 1
|
||||
n = f.freelist[index]
|
||||
f.freelist[index] = nil
|
||||
f.freelist = f.freelist[:index]
|
||||
|
||||
if cap(n.level) < lvl {
|
||||
n.level = make([]skipListLevel, lvl)
|
||||
} else {
|
||||
n.level = n.level[:lvl]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *FreeList) freeNode(n *node) (out bool) {
|
||||
// for gc
|
||||
n.item = nil
|
||||
for j := 0; j < len(n.level); j++ {
|
||||
n.level[j] = skipListLevel{}
|
||||
}
|
||||
|
||||
if len(f.freelist) < cap(f.freelist) {
|
||||
f.freelist = append(f.freelist, n)
|
||||
out = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// skipList represents a skip list
|
||||
type skipList struct {
|
||||
header, tail *node
|
||||
length int
|
||||
level int // current level count
|
||||
maxLevel int
|
||||
freelist *FreeList
|
||||
random *rand.Rand
|
||||
}
|
||||
|
||||
// newSkipList creates a skip list
|
||||
func newSkipList(maxLevel int) *skipList {
|
||||
if maxLevel < DefaultMaxLevel {
|
||||
panic("maxLevel must < 32")
|
||||
}
|
||||
return &skipList{
|
||||
level: 1,
|
||||
header: &node{
|
||||
level: make([]skipListLevel, maxLevel),
|
||||
},
|
||||
maxLevel: maxLevel,
|
||||
freelist: NewFreeList(DefaultFreeListSize),
|
||||
random: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
}
|
||||
}
|
||||
|
||||
// insert an item into the SkipList.
|
||||
func (sl *skipList) insert(item Item) *node {
|
||||
var update [DefaultMaxLevel]*node // [0...list.maxLevel)
|
||||
var rank [DefaultMaxLevel]int
|
||||
x := sl.header
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
if i == sl.level-1 {
|
||||
rank[i] = 0
|
||||
} else {
|
||||
rank[i] = rank[i+1]
|
||||
}
|
||||
for y := x.level[i].forward; y != nil && y.item.Less(item); y = x.level[i].forward {
|
||||
rank[i] += x.level[i].span
|
||||
x = y
|
||||
}
|
||||
update[i] = x
|
||||
}
|
||||
|
||||
lvl := sl.randomLevel()
|
||||
if lvl > sl.level {
|
||||
for i := sl.level; i < lvl; i++ {
|
||||
rank[i] = 0
|
||||
update[i] = sl.header
|
||||
update[i].level[i].span = sl.length
|
||||
}
|
||||
sl.level = lvl
|
||||
}
|
||||
|
||||
x = sl.freelist.newNode(lvl)
|
||||
x.item = item
|
||||
for i := 0; i < lvl; i++ {
|
||||
x.level[i].forward = update[i].level[i].forward
|
||||
update[i].level[i].forward = x
|
||||
|
||||
x.level[i].span = update[i].level[i].span - (rank[0] - rank[i])
|
||||
update[i].level[i].span = (rank[0] - rank[i]) + 1
|
||||
}
|
||||
|
||||
// increment span for untouched levels
|
||||
for i := lvl; i < sl.level; i++ {
|
||||
update[i].level[i].span++
|
||||
}
|
||||
|
||||
if update[0] == sl.header {
|
||||
x.backward = nil
|
||||
} else {
|
||||
x.backward = update[0]
|
||||
}
|
||||
if x.level[0].forward == nil {
|
||||
sl.tail = x
|
||||
} else {
|
||||
x.level[0].forward.backward = x
|
||||
}
|
||||
sl.length++
|
||||
return x
|
||||
}
|
||||
|
||||
// delete element
|
||||
func (sl *skipList) delete(n *node) Item {
|
||||
var preAlloc [DefaultMaxLevel]*node // [0...list.maxLevel)
|
||||
update := preAlloc[:sl.maxLevel]
|
||||
x := sl.header
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
for y := x.level[i].forward; y != nil && y.item.Less(n.item); y = x.level[i].forward {
|
||||
x = y
|
||||
}
|
||||
update[i] = x
|
||||
}
|
||||
x = x.level[0].forward
|
||||
if x != nil && !n.item.Less(x.item) {
|
||||
for i := 0; i < sl.level; i++ {
|
||||
if update[i].level[i].forward == x {
|
||||
update[i].level[i].span += x.level[i].span - 1
|
||||
update[i].level[i].forward = x.level[i].forward
|
||||
} else {
|
||||
update[i].level[i].span--
|
||||
}
|
||||
}
|
||||
for sl.level > 1 && sl.header.level[sl.level-1].forward == nil {
|
||||
sl.level--
|
||||
}
|
||||
if x.level[0].forward == nil {
|
||||
sl.tail = x.backward
|
||||
} else {
|
||||
x.level[0].forward.backward = x.backward
|
||||
}
|
||||
removeItem := x.item
|
||||
sl.freelist.freeNode(x)
|
||||
sl.length--
|
||||
return removeItem
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sl *skipList) updateItem(node *node, item Item) bool {
|
||||
if (node.level[0].forward == nil || !node.level[0].forward.item.Less(item)) &&
|
||||
(node.backward == nil || !item.Less(node.backward.item)) {
|
||||
node.item = item
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getRank find the rank for an element.
|
||||
// Returns 0 when the element cannot be found, rank otherwise.
|
||||
// Note that the rank is 1-based
|
||||
func (sl *skipList) getRank(item Item) int {
|
||||
var rank int
|
||||
x := sl.header
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
for y := x.level[i].forward; y != nil && !item.Less(y.item); y = x.level[i].forward {
|
||||
rank += x.level[i].span
|
||||
x = y
|
||||
}
|
||||
if x.item != nil && !x.item.Less(item) {
|
||||
return rank
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (sl *skipList) randomLevel() int {
|
||||
lvl := 1
|
||||
for lvl < sl.maxLevel && float32(sl.random.Uint32()&0xFFFF) < DefaultP*0xFFFF {
|
||||
lvl++
|
||||
}
|
||||
return lvl
|
||||
}
|
||||
|
||||
// Finds an element by its rank. The rank argument needs to be 1-based.
|
||||
func (sl *skipList) getNodeByRank(rank int) *node {
|
||||
var traversed int
|
||||
x := sl.header
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
for x.level[i].forward != nil && traversed+x.level[i].span <= rank {
|
||||
traversed += x.level[i].span
|
||||
x = x.level[i].forward
|
||||
}
|
||||
if traversed == rank {
|
||||
return x
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sl *skipList) getMinNode() *node {
|
||||
return sl.header.level[0].forward
|
||||
}
|
||||
|
||||
func (sl *skipList) getMaxNode() *node {
|
||||
return sl.tail
|
||||
}
|
||||
|
||||
// return the first node greater and the node's 1-based rank.
|
||||
func (sl *skipList) findNext(greater func(i Item) bool) (*node, int) {
|
||||
x := sl.header
|
||||
var rank int
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
for y := x.level[i].forward; y != nil && !greater(y.item); y = x.level[i].forward {
|
||||
rank += x.level[i].span
|
||||
x = y
|
||||
}
|
||||
}
|
||||
return x.level[0].forward, rank + x.level[0].span
|
||||
}
|
||||
|
||||
// return the first node less and the node's 1-based rank.
|
||||
func (sl *skipList) findPrev(less func(i Item) bool) (*node, int) {
|
||||
var rank int
|
||||
x := sl.header
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
for y := x.level[i].forward; y != nil && less(y.item); y = x.level[i].forward {
|
||||
rank += x.level[i].span
|
||||
x = y
|
||||
}
|
||||
}
|
||||
return x, rank
|
||||
}
|
||||
|
||||
// ZSet set
|
||||
type ZSet struct {
|
||||
dict map[string]*node
|
||||
sl *skipList
|
||||
}
|
||||
|
||||
// New creates a new ZSet.
|
||||
func New() *ZSet {
|
||||
return &ZSet{
|
||||
dict: make(map[string]*node),
|
||||
sl: newSkipList(DefaultMaxLevel),
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new element or update the score of an existing element. If an item already
|
||||
// exist, the removed item is returned. Otherwise, nil is returned.
|
||||
func (zs *ZSet) Add(key string, item Item) (removeItem Item) {
|
||||
if node := zs.dict[key]; node != nil {
|
||||
// if the node after update, would be still exactly at the same position,
|
||||
// we can just update item.
|
||||
if zs.sl.updateItem(node, item) {
|
||||
return
|
||||
}
|
||||
removeItem = zs.sl.delete(node)
|
||||
}
|
||||
zs.dict[key] = zs.sl.insert(item)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove the element 'ele' from the sorted set,
|
||||
// return true if the element existed and was deleted, false otherwise
|
||||
func (zs *ZSet) Remove(key string) (removeItem Item) {
|
||||
node := zs.dict[key]
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
removeItem = zs.sl.delete(node)
|
||||
delete(zs.dict, key)
|
||||
return
|
||||
}
|
||||
|
||||
// Rank return 1-based rank or 0 if not exist
|
||||
func (zs *ZSet) Rank(key string, reverse bool) int {
|
||||
node := zs.dict[key]
|
||||
if node != nil {
|
||||
rank := zs.sl.getRank(node.item)
|
||||
if rank > 0 {
|
||||
if reverse {
|
||||
return zs.sl.length - rank + 1
|
||||
}
|
||||
return rank
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (zs *ZSet) FindNext(iGreaterThan func(i Item) bool) (v Item, rank int) {
|
||||
n, rank := zs.sl.findNext(iGreaterThan)
|
||||
if n == nil {
|
||||
return
|
||||
}
|
||||
return n.item, rank
|
||||
}
|
||||
|
||||
func (zs *ZSet) FindPrev(iLessThan func(i Item) bool) (v Item, rank int) {
|
||||
n, rank := zs.sl.findPrev(iLessThan)
|
||||
if n == nil {
|
||||
return
|
||||
}
|
||||
return n.item, rank
|
||||
}
|
||||
|
||||
// RangeByScore calls the iterator for every value within the range [min, max],
|
||||
// until iterator return false. If min is nil, it represents negative infinity.
|
||||
// If max is nil, it represents positive infinity.
|
||||
func (zs *ZSet) RangeByScore(min, max func(i Item) bool, reverse bool, iterator ItemIterator) {
|
||||
llen := zs.sl.length
|
||||
var minNode, maxNode *node
|
||||
var minRank, maxRank int
|
||||
if min == nil {
|
||||
minNode = zs.sl.getMinNode()
|
||||
minRank = 1
|
||||
} else {
|
||||
minNode, minRank = zs.sl.findNext(min)
|
||||
}
|
||||
if minNode == nil {
|
||||
return
|
||||
}
|
||||
if max == nil {
|
||||
maxNode = zs.sl.getMaxNode()
|
||||
maxRank = llen
|
||||
} else {
|
||||
maxNode, maxRank = zs.sl.findPrev(max)
|
||||
}
|
||||
if maxNode == nil {
|
||||
return
|
||||
}
|
||||
if reverse {
|
||||
n := maxNode
|
||||
for i := maxRank; i >= minRank; i-- {
|
||||
if iterator(n.item, llen-i+1) {
|
||||
n = n.backward
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
n := minNode
|
||||
for i := minRank; i <= maxRank; i++ {
|
||||
if iterator(n.item, i) {
|
||||
n = n.level[0].forward
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Range calls the iterator for every value with in index range [start, end],
|
||||
// until iterator return false. The <start> and <stop> arguments represent
|
||||
// zero-based indexes.
|
||||
func (zs *ZSet) Range(start, end int, reverse bool, iterator ItemIterator) {
|
||||
llen := zs.sl.length
|
||||
if start < 0 {
|
||||
start = llen + start
|
||||
}
|
||||
if end < 0 {
|
||||
end = llen + end
|
||||
}
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
if start > end || start >= llen {
|
||||
return
|
||||
}
|
||||
if end >= llen {
|
||||
end = llen - 1
|
||||
}
|
||||
|
||||
rangeLen := end - start + 1
|
||||
if reverse {
|
||||
ln := zs.sl.getNodeByRank(llen - start)
|
||||
for i := 1; i <= rangeLen; i++ {
|
||||
if iterator(ln.item, start+i) {
|
||||
ln = ln.backward
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ln := zs.sl.getNodeByRank(start + 1)
|
||||
for i := 1; i <= rangeLen; i++ {
|
||||
if iterator(ln.item, start+i) {
|
||||
ln = ln.level[0].forward
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type RangeIterator struct {
|
||||
node *node
|
||||
start, end, cur int
|
||||
reverse bool
|
||||
}
|
||||
|
||||
func (r *RangeIterator) Len() int {
|
||||
return r.end - r.start + 1
|
||||
}
|
||||
|
||||
func (r *RangeIterator) Valid() bool {
|
||||
return r.cur <= r.end
|
||||
}
|
||||
|
||||
func (r *RangeIterator) Next() {
|
||||
if r.reverse {
|
||||
r.node = r.node.backward
|
||||
} else {
|
||||
r.node = r.node.level[0].forward
|
||||
}
|
||||
r.cur++
|
||||
}
|
||||
|
||||
func (r *RangeIterator) Item() Item {
|
||||
return r.node.item
|
||||
}
|
||||
|
||||
func (r *RangeIterator) Rank() int {
|
||||
return r.cur + 1
|
||||
}
|
||||
|
||||
// RangeIterator return iterator for visit elements in [start, end].
|
||||
// It is slower than Range.
|
||||
func (zs *ZSet) RangeIterator(start, end int, reverse bool) RangeIterator {
|
||||
llen := zs.sl.length
|
||||
if start < 0 {
|
||||
start = llen + start
|
||||
}
|
||||
if end < 0 {
|
||||
end = llen + end
|
||||
}
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
|
||||
if start > end || start >= llen {
|
||||
return RangeIterator{end: -1}
|
||||
}
|
||||
|
||||
if end >= llen {
|
||||
end = llen - 1
|
||||
}
|
||||
|
||||
var n *node
|
||||
if reverse {
|
||||
n = zs.sl.getNodeByRank(llen - start)
|
||||
} else {
|
||||
n = zs.sl.getNodeByRank(start + 1)
|
||||
}
|
||||
return RangeIterator{
|
||||
start: start,
|
||||
cur: start,
|
||||
end: end,
|
||||
node: n,
|
||||
reverse: reverse,
|
||||
}
|
||||
}
|
||||
|
||||
// Get return Item in dict.
|
||||
func (zs *ZSet) Get(key string) Item {
|
||||
if node, ok := zs.dict[key]; ok {
|
||||
return node.item
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Length return the element count
|
||||
func (zs *ZSet) Length() int {
|
||||
return zs.sl.length
|
||||
}
|
||||
|
||||
type Int int
|
||||
|
||||
func (a Int) Key() string {
|
||||
return strconv.Itoa(int(a))
|
||||
}
|
||||
|
||||
func (a Int) Less(b Item) bool {
|
||||
return a < b.(Int)
|
||||
}
|
||||
529
common/utils/zset/zset_generic.go
Normal file
529
common/utils/zset/zset_generic.go
Normal file
@@ -0,0 +1,529 @@
|
||||
//go:build go1.18
|
||||
|
||||
// Package zset implements sorted set similar to redis zset.
|
||||
package zset
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultMaxLevel = 32 // (1/p)^MaxLevel >= maxNode
|
||||
DefaultP = 0.25 // SkipList P = 1/4
|
||||
|
||||
DefaultFreeListSize = 32
|
||||
)
|
||||
|
||||
// ItemIterator allows callers of Range* to iterate of the zset.
|
||||
// When this function returns false, iteration will stop.
|
||||
type ItemIterator[T any] func(i T, rank int) bool
|
||||
|
||||
type skipListLevel[T any] struct {
|
||||
forward *node[T]
|
||||
span int
|
||||
}
|
||||
|
||||
// node is an element of a skip list
|
||||
type node[T any] struct {
|
||||
item T
|
||||
backward *node[T]
|
||||
level []skipListLevel[T]
|
||||
}
|
||||
|
||||
// FreeList represents a free list of set node.
|
||||
type FreeList[T any] struct {
|
||||
freelist []*node[T]
|
||||
}
|
||||
|
||||
// NewFreeList creates a new free list.
|
||||
func NewFreeList[T any](size int) *FreeList[T] {
|
||||
return &FreeList[T]{freelist: make([]*node[T], 0, size)}
|
||||
}
|
||||
|
||||
func (f *FreeList[T]) newNode(lvl int) (n *node[T]) {
|
||||
if len(f.freelist) == 0 {
|
||||
n = new(node[T])
|
||||
n.level = make([]skipListLevel[T], lvl)
|
||||
return
|
||||
}
|
||||
index := len(f.freelist) - 1
|
||||
n = f.freelist[index]
|
||||
f.freelist[index] = nil
|
||||
f.freelist = f.freelist[:index]
|
||||
|
||||
if cap(n.level) < lvl {
|
||||
n.level = make([]skipListLevel[T], lvl)
|
||||
} else {
|
||||
n.level = n.level[:lvl]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *FreeList[T]) freeNode(n *node[T]) (out bool) {
|
||||
// for gc
|
||||
var zero T
|
||||
n.item = zero
|
||||
for j := 0; j < len(n.level); j++ {
|
||||
n.level[j] = skipListLevel[T]{}
|
||||
}
|
||||
|
||||
if len(f.freelist) < cap(f.freelist) {
|
||||
f.freelist = append(f.freelist, n)
|
||||
out = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// skipList represents a skip list
|
||||
type skipList[T any] struct {
|
||||
header, tail *node[T]
|
||||
length int
|
||||
level int // current level count
|
||||
maxLevel int
|
||||
freelist *FreeList[T]
|
||||
random *rand.Rand
|
||||
less LessFunc[T]
|
||||
}
|
||||
|
||||
// newSkipList creates a skip list
|
||||
func newSkipList[T any](maxLevel int, less LessFunc[T]) *skipList[T] {
|
||||
if maxLevel < DefaultMaxLevel {
|
||||
panic("maxLevel must < 32")
|
||||
}
|
||||
return &skipList[T]{
|
||||
level: 1,
|
||||
header: &node[T]{
|
||||
level: make([]skipListLevel[T], maxLevel),
|
||||
},
|
||||
maxLevel: maxLevel,
|
||||
freelist: NewFreeList[T](DefaultFreeListSize),
|
||||
random: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
less: less,
|
||||
}
|
||||
}
|
||||
|
||||
// insert an item into the SkipList.
|
||||
func (sl *skipList[T]) insert(item T) *node[T] {
|
||||
var update [DefaultMaxLevel]*node[T] // [0...list.maxLevel)
|
||||
var rank [DefaultMaxLevel]int
|
||||
x := sl.header
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
if i == sl.level-1 {
|
||||
rank[i] = 0
|
||||
} else {
|
||||
rank[i] = rank[i+1]
|
||||
}
|
||||
for y := x.level[i].forward; y != nil && sl.less(y.item, item); y = x.level[i].forward {
|
||||
rank[i] += x.level[i].span
|
||||
x = y
|
||||
}
|
||||
update[i] = x
|
||||
}
|
||||
|
||||
lvl := sl.randomLevel()
|
||||
if lvl > sl.level {
|
||||
for i := sl.level; i < lvl; i++ {
|
||||
rank[i] = 0
|
||||
update[i] = sl.header
|
||||
update[i].level[i].span = sl.length
|
||||
}
|
||||
sl.level = lvl
|
||||
}
|
||||
|
||||
x = sl.freelist.newNode(lvl)
|
||||
x.item = item
|
||||
for i := 0; i < lvl; i++ {
|
||||
x.level[i].forward = update[i].level[i].forward
|
||||
update[i].level[i].forward = x
|
||||
|
||||
x.level[i].span = update[i].level[i].span - (rank[0] - rank[i])
|
||||
update[i].level[i].span = (rank[0] - rank[i]) + 1
|
||||
}
|
||||
|
||||
// increment span for untouched levels
|
||||
for i := lvl; i < sl.level; i++ {
|
||||
update[i].level[i].span++
|
||||
}
|
||||
|
||||
if update[0] == sl.header {
|
||||
x.backward = nil
|
||||
} else {
|
||||
x.backward = update[0]
|
||||
}
|
||||
if x.level[0].forward == nil {
|
||||
sl.tail = x
|
||||
} else {
|
||||
x.level[0].forward.backward = x
|
||||
}
|
||||
sl.length++
|
||||
return x
|
||||
}
|
||||
|
||||
// delete element
|
||||
func (sl *skipList[T]) delete(n *node[T]) (_ T) {
|
||||
var preAlloc [DefaultMaxLevel]*node[T] // [0...list.maxLevel)
|
||||
update := preAlloc[:sl.maxLevel]
|
||||
x := sl.header
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
for y := x.level[i].forward; y != nil && sl.less(y.item, n.item); y = x.level[i].forward {
|
||||
x = y
|
||||
}
|
||||
update[i] = x
|
||||
}
|
||||
x = x.level[0].forward
|
||||
if x != nil && !sl.less(n.item, x.item) {
|
||||
for i := 0; i < sl.level; i++ {
|
||||
if update[i].level[i].forward == x {
|
||||
update[i].level[i].span += x.level[i].span - 1
|
||||
update[i].level[i].forward = x.level[i].forward
|
||||
} else {
|
||||
update[i].level[i].span--
|
||||
}
|
||||
}
|
||||
for sl.level > 1 && sl.header.level[sl.level-1].forward == nil {
|
||||
sl.level--
|
||||
}
|
||||
if x.level[0].forward == nil {
|
||||
sl.tail = x.backward
|
||||
} else {
|
||||
x.level[0].forward.backward = x.backward
|
||||
}
|
||||
removeItem := x.item
|
||||
sl.freelist.freeNode(x)
|
||||
sl.length--
|
||||
return removeItem
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (sl *skipList[T]) updateItem(node *node[T], item T) bool {
|
||||
if (node.level[0].forward == nil || !sl.less(node.level[0].forward.item, item)) &&
|
||||
(node.backward == nil || !sl.less(item, node.backward.item)) {
|
||||
node.item = item
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// getRank find the rank for an element.
|
||||
// Returns 0 when the element cannot be found, rank otherwise.
|
||||
// Note that the rank is 1-based
|
||||
func (sl *skipList[T]) getRank(item T) int {
|
||||
var rank int
|
||||
x := sl.header
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
for y := x.level[i].forward; y != nil && !sl.less(item, y.item); y = x.level[i].forward {
|
||||
rank += x.level[i].span
|
||||
x = y
|
||||
}
|
||||
if x != sl.header && !sl.less(x.item, item) {
|
||||
return rank
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (sl *skipList[T]) randomLevel() int {
|
||||
lvl := 1
|
||||
for lvl < sl.maxLevel && float32(sl.random.Uint32()&0xFFFF) < DefaultP*0xFFFF {
|
||||
lvl++
|
||||
}
|
||||
return lvl
|
||||
}
|
||||
|
||||
// Finds an element by its rank. The rank argument needs to be 1-based.
|
||||
func (sl *skipList[T]) getNodeByRank(rank int) *node[T] {
|
||||
var traversed int
|
||||
x := sl.header
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
for x.level[i].forward != nil && traversed+x.level[i].span <= rank {
|
||||
traversed += x.level[i].span
|
||||
x = x.level[i].forward
|
||||
}
|
||||
if traversed == rank {
|
||||
return x
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sl *skipList[T]) getMinNode() *node[T] {
|
||||
return sl.header.level[0].forward
|
||||
}
|
||||
|
||||
func (sl *skipList[T]) getMaxNode() *node[T] {
|
||||
return sl.tail
|
||||
}
|
||||
|
||||
// return the first node greater and the node's 1-based rank.
|
||||
func (sl *skipList[T]) findNext(greater func(i T) bool) (*node[T], int) {
|
||||
x := sl.header
|
||||
var rank int
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
for y := x.level[i].forward; y != nil && !greater(y.item); y = x.level[i].forward {
|
||||
rank += x.level[i].span
|
||||
x = y
|
||||
}
|
||||
}
|
||||
return x.level[0].forward, rank + x.level[0].span
|
||||
}
|
||||
|
||||
// return the first node less and the node's 1-based rank.
|
||||
func (sl *skipList[T]) findPrev(less func(i T) bool) (*node[T], int) {
|
||||
var rank int
|
||||
x := sl.header
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
for y := x.level[i].forward; y != nil && less(y.item); y = x.level[i].forward {
|
||||
rank += x.level[i].span
|
||||
x = y
|
||||
}
|
||||
}
|
||||
return x, rank
|
||||
}
|
||||
|
||||
// ZSet set
|
||||
type ZSet[K comparable, T any] struct {
|
||||
dict map[K]*node[T]
|
||||
sl *skipList[T]
|
||||
}
|
||||
|
||||
// LessFunc determines how to order a type 'T'. It should implement a strict
|
||||
// ordering, and should return true if within that ordering, 'a' < 'b'.
|
||||
type LessFunc[T any] func(a, b T) bool
|
||||
|
||||
// New creates a new ZSet.
|
||||
func New[K comparable, T any](less LessFunc[T]) *ZSet[K, T] {
|
||||
return &ZSet[K, T]{
|
||||
dict: make(map[K]*node[T]),
|
||||
sl: newSkipList[T](DefaultMaxLevel, less),
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new element or update the score of an existing element. If an item already
|
||||
// exist, the removed item is returned. Otherwise, nil is returned.
|
||||
func (zs *ZSet[K, T]) Add(key K, item T) (removeItem T) {
|
||||
if node := zs.dict[key]; node != nil {
|
||||
// if the node after update, would be still exactly at the same position,
|
||||
// we can just update item.
|
||||
if zs.sl.updateItem(node, item) {
|
||||
return
|
||||
}
|
||||
removeItem = zs.sl.delete(node)
|
||||
}
|
||||
zs.dict[key] = zs.sl.insert(item)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove the element 'ele' from the sorted set,
|
||||
// return true if the element existed and was deleted, false otherwise
|
||||
func (zs *ZSet[K, T]) Remove(key K) (removeItem T) {
|
||||
node := zs.dict[key]
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
removeItem = zs.sl.delete(node)
|
||||
delete(zs.dict, key)
|
||||
return
|
||||
}
|
||||
|
||||
// Rank return 1-based rank or 0 if not exist
|
||||
func (zs *ZSet[K, T]) Rank(key K, reverse bool) int {
|
||||
node := zs.dict[key]
|
||||
if node != nil {
|
||||
rank := zs.sl.getRank(node.item)
|
||||
if rank > 0 {
|
||||
if reverse {
|
||||
return zs.sl.length - rank + 1
|
||||
}
|
||||
return rank
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (zs *ZSet[K, T]) FindNext(iGreaterThan func(i T) bool) (v T, rank int) {
|
||||
n, rank := zs.sl.findNext(iGreaterThan)
|
||||
if n == nil {
|
||||
return
|
||||
}
|
||||
return n.item, rank
|
||||
}
|
||||
|
||||
func (zs *ZSet[K, T]) FindPrev(iLessThan func(i T) bool) (v T, rank int) {
|
||||
n, rank := zs.sl.findPrev(iLessThan)
|
||||
if n == nil {
|
||||
return
|
||||
}
|
||||
return n.item, rank
|
||||
}
|
||||
|
||||
// RangeByScore calls the iterator for every value within the range [min, max],
|
||||
// until iterator return false. If min is nil, it represents negative infinity.
|
||||
// If max is nil, it represents positive infinity.
|
||||
func (zs *ZSet[K, T]) RangeByScore(min, max func(i T) bool, reverse bool, iterator ItemIterator[T]) {
|
||||
llen := zs.sl.length
|
||||
var minNode, maxNode *node[T]
|
||||
var minRank, maxRank int
|
||||
if min == nil {
|
||||
minNode = zs.sl.getMinNode()
|
||||
minRank = 1
|
||||
} else {
|
||||
minNode, minRank = zs.sl.findNext(min)
|
||||
}
|
||||
if minNode == nil {
|
||||
return
|
||||
}
|
||||
if max == nil {
|
||||
maxNode = zs.sl.getMaxNode()
|
||||
maxRank = llen
|
||||
} else {
|
||||
maxNode, maxRank = zs.sl.findPrev(max)
|
||||
}
|
||||
if maxNode == nil {
|
||||
return
|
||||
}
|
||||
if reverse {
|
||||
n := maxNode
|
||||
for i := maxRank; i >= minRank; i-- {
|
||||
if iterator(n.item, llen-i+1) {
|
||||
n = n.backward
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
n := minNode
|
||||
for i := minRank; i <= maxRank; i++ {
|
||||
if iterator(n.item, i) {
|
||||
n = n.level[0].forward
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Range calls the iterator for every value with in index range [start, end],
|
||||
// until iterator return false. The <start> and <stop> arguments represent
|
||||
// zero-based indexes.
|
||||
func (zs *ZSet[K, T]) Range(start, end int, reverse bool, iterator ItemIterator[T]) {
|
||||
llen := zs.sl.length
|
||||
if start < 0 {
|
||||
start = llen + start
|
||||
}
|
||||
if end < 0 {
|
||||
end = llen + end
|
||||
}
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
if start > end || start >= llen {
|
||||
return
|
||||
}
|
||||
if end >= llen {
|
||||
end = llen - 1
|
||||
}
|
||||
|
||||
rangeLen := end - start + 1
|
||||
if reverse {
|
||||
ln := zs.sl.getNodeByRank(llen - start)
|
||||
for i := 1; i <= rangeLen; i++ {
|
||||
if iterator(ln.item, start+i) {
|
||||
ln = ln.backward
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ln := zs.sl.getNodeByRank(start + 1)
|
||||
for i := 1; i <= rangeLen; i++ {
|
||||
if iterator(ln.item, start+i) {
|
||||
ln = ln.level[0].forward
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type RangeIterator[T any] struct {
|
||||
node *node[T]
|
||||
start, end, cur int
|
||||
reverse bool
|
||||
}
|
||||
|
||||
func (r *RangeIterator[T]) Len() int {
|
||||
return r.end - r.start + 1
|
||||
}
|
||||
|
||||
func (r *RangeIterator[T]) Valid() bool {
|
||||
return r.cur <= r.end
|
||||
}
|
||||
|
||||
func (r *RangeIterator[T]) Next() {
|
||||
if r.reverse {
|
||||
r.node = r.node.backward
|
||||
} else {
|
||||
r.node = r.node.level[0].forward
|
||||
}
|
||||
r.cur++
|
||||
}
|
||||
|
||||
func (r *RangeIterator[T]) Item() T {
|
||||
return r.node.item
|
||||
}
|
||||
|
||||
func (r *RangeIterator[T]) Rank() int {
|
||||
return r.cur + 1
|
||||
}
|
||||
|
||||
// RangeIterator return iterator for visit elements in [start, end].
|
||||
// It is slower than Range.
|
||||
func (zs *ZSet[K, T]) RangeIterator(start, end int, reverse bool) RangeIterator[T] {
|
||||
llen := zs.sl.length
|
||||
if start < 0 {
|
||||
start = llen + start
|
||||
}
|
||||
if end < 0 {
|
||||
end = llen + end
|
||||
}
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
|
||||
if start > end || start >= llen {
|
||||
return RangeIterator[T]{end: -1}
|
||||
}
|
||||
|
||||
if end >= llen {
|
||||
end = llen - 1
|
||||
}
|
||||
|
||||
var n *node[T]
|
||||
if reverse {
|
||||
n = zs.sl.getNodeByRank(llen - start)
|
||||
} else {
|
||||
n = zs.sl.getNodeByRank(start + 1)
|
||||
}
|
||||
return RangeIterator[T]{
|
||||
start: start,
|
||||
cur: start,
|
||||
end: end,
|
||||
node: n,
|
||||
reverse: reverse,
|
||||
}
|
||||
}
|
||||
|
||||
// Get return Item in dict.
|
||||
func (zs *ZSet[K, T]) Get(key K) (item T, found bool) {
|
||||
if n, ok := zs.dict[key]; ok {
|
||||
return n.item, ok
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Length return the element count
|
||||
func (zs *ZSet[K, T]) Length() int {
|
||||
return zs.sl.length
|
||||
}
|
||||
342
common/utils/zset/zset_generic_test.go
Normal file
342
common/utils/zset/zset_generic_test.go
Normal file
@@ -0,0 +1,342 @@
|
||||
//go:build go1.18
|
||||
|
||||
package zset
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type TestRank struct {
|
||||
member string
|
||||
score int
|
||||
}
|
||||
|
||||
// perm returns a random permutation of n Int items in the range [0, n).
|
||||
func perm(n int) (out []TestRank) {
|
||||
out = make([]TestRank, 0, n)
|
||||
for _, v := range rand.Perm(n) {
|
||||
out = append(out, TestRank{
|
||||
member: strconv.Itoa(v),
|
||||
score: v,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// rang returns an ordered list of Int items in the range [0, n).
|
||||
func rang(n int) (out []TestRank) {
|
||||
for i := 0; i < n; i++ {
|
||||
out = append(out, TestRank{
|
||||
member: strconv.Itoa(i),
|
||||
score: i,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func revrang(n int, count int) (out []TestRank) {
|
||||
for i := n - 1; i >= n-count; i-- {
|
||||
out = append(out, TestRank{
|
||||
member: strconv.Itoa(i),
|
||||
score: i,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestZSetRank(t *testing.T) {
|
||||
const listSize = 10000
|
||||
zs := New[string, TestRank](func(a, b TestRank) bool {
|
||||
return a.score < b.score
|
||||
})
|
||||
for i := 0; i < 10; i++ {
|
||||
for _, v := range perm(listSize) {
|
||||
zs.Add(v.member, v)
|
||||
}
|
||||
for _, v := range perm(listSize) {
|
||||
if zs.Rank(v.member, false) != v.score+1 {
|
||||
t.Error("rank error")
|
||||
}
|
||||
if zs.Rank(v.member, true) != listSize-v.score {
|
||||
t.Error("rank error")
|
||||
}
|
||||
}
|
||||
|
||||
var r []TestRank
|
||||
zs.Range(0, 1, false, func(item TestRank, _ int) bool {
|
||||
r = append(r, item)
|
||||
return true
|
||||
})
|
||||
if !reflect.DeepEqual(r, rang(2)) {
|
||||
t.Error("range error")
|
||||
}
|
||||
|
||||
r = r[:0]
|
||||
zs.RangeByScore(func(i TestRank) bool {
|
||||
return i.score >= 0
|
||||
}, func(i TestRank) bool {
|
||||
return i.score <= 1
|
||||
}, false, func(item TestRank, rank int) bool {
|
||||
r = append(r, item)
|
||||
return true
|
||||
})
|
||||
if !reflect.DeepEqual(r, rang(2)) {
|
||||
t.Error("RangeItem error", r, rang(2))
|
||||
}
|
||||
|
||||
r = r[:0]
|
||||
zs.Range(0, 1, true, func(item TestRank, _ int) bool {
|
||||
r = append(r, item)
|
||||
return true
|
||||
})
|
||||
if !reflect.DeepEqual(r, revrang(listSize, 2)) {
|
||||
t.Error("range error")
|
||||
}
|
||||
|
||||
for i := 0; i < listSize/2; i++ {
|
||||
zs.Remove(strconv.Itoa(i))
|
||||
}
|
||||
for i := listSize + 1; i < listSize; i++ {
|
||||
if r := zs.Rank(strconv.Itoa(i), false); r != i-listSize/2 {
|
||||
t.Error("rank failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRangeItem(t *testing.T) {
|
||||
zs := New[string, TestRank](func(a, b TestRank) bool {
|
||||
return a.score < b.score
|
||||
})
|
||||
zs.RangeByScore(nil, nil, false, func(i TestRank, rank int) bool {
|
||||
return true
|
||||
})
|
||||
|
||||
for _, i := range perm(10) {
|
||||
zs.Add(i.member, i)
|
||||
}
|
||||
|
||||
var r []TestRank
|
||||
zs.RangeByScore(nil, nil, false, func(i TestRank, rank int) bool {
|
||||
r = append(r, i)
|
||||
return true
|
||||
})
|
||||
if !reflect.DeepEqual(r, rang(10)) {
|
||||
t.Error("RangeItem error", r, rang(10))
|
||||
}
|
||||
|
||||
r = r[:0]
|
||||
zs.RangeByScore(func(i TestRank) bool {
|
||||
return i.score >= 3
|
||||
}, func(i TestRank) bool {
|
||||
return i.score <= 5
|
||||
}, false, func(i TestRank, rank int) bool {
|
||||
r = append(r, i)
|
||||
return true
|
||||
})
|
||||
var expect []TestRank
|
||||
for i := 3; i <= 5; i++ {
|
||||
expect = append(expect, TestRank{
|
||||
member: strconv.Itoa(i),
|
||||
score: i,
|
||||
})
|
||||
}
|
||||
if !reflect.DeepEqual(r, expect) {
|
||||
t.Error("RangeItem error", r, expect)
|
||||
}
|
||||
|
||||
r = r[:0]
|
||||
zs.RangeByScore(func(i TestRank) bool {
|
||||
return i.score >= 3
|
||||
}, func(i TestRank) bool {
|
||||
return i.score <= 5
|
||||
}, true, func(i TestRank, rank int) bool {
|
||||
r = append(r, i)
|
||||
return true
|
||||
})
|
||||
expect = expect[:0]
|
||||
for i := 5; i >= 3; i-- {
|
||||
expect = append(expect, TestRank{
|
||||
member: strconv.Itoa(i),
|
||||
score: i,
|
||||
})
|
||||
}
|
||||
if !reflect.DeepEqual(r, expect) {
|
||||
t.Error("RangeItem error", r, expect)
|
||||
}
|
||||
}
|
||||
|
||||
const benchmarkListSize = 10000
|
||||
|
||||
func BenchmarkAdd(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := perm(benchmarkListSize)
|
||||
b.StartTimer()
|
||||
i := 0
|
||||
for i < b.N {
|
||||
tr := New[string, TestRank](func(a, b TestRank) bool {
|
||||
return a.score < b.score
|
||||
})
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.member, item)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAddIncrease(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := rang(benchmarkListSize)
|
||||
b.StartTimer()
|
||||
i := 0
|
||||
for i < b.N {
|
||||
tr := New[string, TestRank](func(a, b TestRank) bool {
|
||||
return a.score < b.score
|
||||
})
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.member, item)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAddDecrease(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := revrang(benchmarkListSize, benchmarkListSize)
|
||||
b.StartTimer()
|
||||
i := 0
|
||||
for i < b.N {
|
||||
tr := New[string, TestRank](func(a, b TestRank) bool {
|
||||
return a.score < b.score
|
||||
})
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.member, item)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRemoveAdd(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := perm(benchmarkListSize)
|
||||
tr := New[string, TestRank](func(a, b TestRank) bool {
|
||||
return a.score < b.score
|
||||
})
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.member, item)
|
||||
}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr.Remove(insertP[i%benchmarkListSize].member)
|
||||
item := insertP[i%benchmarkListSize]
|
||||
tr.Add(item.member, item)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRemove(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := perm(benchmarkListSize)
|
||||
removeP := perm(benchmarkListSize)
|
||||
b.StartTimer()
|
||||
i := 0
|
||||
for i < b.N {
|
||||
b.StopTimer()
|
||||
tr := New[string, TestRank](func(a, b TestRank) bool {
|
||||
return a.score < b.score
|
||||
})
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.member, item)
|
||||
}
|
||||
b.StartTimer()
|
||||
for _, item := range removeP {
|
||||
tr.Remove(item.member)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
if tr.Length() > 0 {
|
||||
b.Error(tr.Length())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRank(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := perm(benchmarkListSize)
|
||||
tr := New[string, TestRank](func(a, b TestRank) bool {
|
||||
return a.score < b.score
|
||||
})
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.member, item)
|
||||
}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr.Rank(insertP[i%benchmarkListSize].member, true)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRange(b *testing.B) {
|
||||
insertP := perm(benchmarkListSize)
|
||||
tr := New[string, TestRank](func(a, b TestRank) bool {
|
||||
return a.score < b.score
|
||||
})
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.member, item)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr.Range(0, 100, true, func(i TestRank, rank int) bool {
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRangeIterator(b *testing.B) {
|
||||
insertP := perm(benchmarkListSize)
|
||||
tr := New[string, TestRank](func(a, b TestRank) bool {
|
||||
return a.score < b.score
|
||||
})
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.member, item)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
it := tr.RangeIterator(0, 100, true)
|
||||
for ; it.Valid(); it.Next() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRangeItem(b *testing.B) {
|
||||
insertP := perm(benchmarkListSize)
|
||||
tr := New[string, TestRank](func(a, b TestRank) bool {
|
||||
return a.score < b.score
|
||||
})
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.member, item)
|
||||
}
|
||||
minScore, maxScore := 0, 100
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr.RangeByScore(func(i TestRank) bool {
|
||||
return i.score >= minScore
|
||||
}, func(i TestRank) bool {
|
||||
return i.score <= maxScore
|
||||
}, true, func(i TestRank, rank int) bool {
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
329
common/utils/zset/zset_test.go
Normal file
329
common/utils/zset/zset_test.go
Normal file
@@ -0,0 +1,329 @@
|
||||
//go:build !go1.18
|
||||
// +build !go1.18
|
||||
|
||||
package zset
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type TestRank struct {
|
||||
member string
|
||||
score int
|
||||
}
|
||||
|
||||
func (a TestRank) Key() string {
|
||||
return a.member
|
||||
}
|
||||
|
||||
func (a TestRank) Less(than Item) bool {
|
||||
return a.score < than.(TestRank).score
|
||||
}
|
||||
|
||||
// perm returns a random permutation of n Int items in the range [0, n).
|
||||
func perm(n int) (out []TestRank) {
|
||||
out = make([]TestRank, 0, n)
|
||||
for _, v := range rand.Perm(n) {
|
||||
out = append(out, TestRank{
|
||||
member: strconv.Itoa(v),
|
||||
score: v,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// rang returns an ordered list of Int items in the range [0, n).
|
||||
func rang(n int) (out []TestRank) {
|
||||
for i := 0; i < n; i++ {
|
||||
out = append(out, TestRank{
|
||||
member: strconv.Itoa(i),
|
||||
score: i,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func revrang(n int, count int) (out []TestRank) {
|
||||
for i := n - 1; i >= n-count; i-- {
|
||||
out = append(out, TestRank{
|
||||
member: strconv.Itoa(i),
|
||||
score: i,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestZSetRank(t *testing.T) {
|
||||
const listSize = 10000
|
||||
zs := New()
|
||||
for i := 0; i < 10; i++ {
|
||||
for _, v := range perm(listSize) {
|
||||
zs.Add(v.member, v)
|
||||
}
|
||||
for _, v := range perm(listSize) {
|
||||
if zs.Rank(v.Key(), false) != v.score+1 {
|
||||
t.Error("rank error")
|
||||
}
|
||||
if zs.Rank(v.Key(), true) != listSize-v.score {
|
||||
t.Error("rank error")
|
||||
}
|
||||
}
|
||||
|
||||
var r []Item
|
||||
zs.Range(0, 1, false, func(item Item, _ int) bool {
|
||||
r = append(r, item)
|
||||
return true
|
||||
})
|
||||
if !reflect.DeepEqual(r, rang(2)) {
|
||||
t.Error("range error")
|
||||
}
|
||||
|
||||
r = r[:0]
|
||||
zs.RangeByScore(func(i Item) bool {
|
||||
return i.(TestRank).score >= 0
|
||||
}, func(i Item) bool {
|
||||
return i.(TestRank).score <= 1
|
||||
}, false, func(item Item, rank int) bool {
|
||||
r = append(r, item)
|
||||
return true
|
||||
})
|
||||
if !reflect.DeepEqual(r, rang(2)) {
|
||||
t.Error("RangeItem error", r, rang(2))
|
||||
}
|
||||
|
||||
r = r[:0]
|
||||
zs.Range(0, 1, true, func(item Item, _ int) bool {
|
||||
r = append(r, item)
|
||||
return true
|
||||
})
|
||||
if !reflect.DeepEqual(r, revrang(listSize, 2)) {
|
||||
t.Error("range error")
|
||||
}
|
||||
|
||||
for i := 0; i < listSize/2; i++ {
|
||||
zs.Remove(strconv.Itoa(i))
|
||||
}
|
||||
for i := listSize + 1; i < listSize; i++ {
|
||||
if r := zs.Rank(strconv.Itoa(i), false); r != i-listSize/2 {
|
||||
t.Error("rank failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRangeItem(t *testing.T) {
|
||||
zs := New()
|
||||
zs.RangeByScore(nil, nil, false, func(i Item, rank int) bool {
|
||||
return true
|
||||
})
|
||||
|
||||
for _, i := range perm(10) {
|
||||
zs.Add(i.member, i)
|
||||
}
|
||||
|
||||
var r []Item
|
||||
zs.RangeByScore(nil, nil, false, func(i Item, rank int) bool {
|
||||
r = append(r, i)
|
||||
return true
|
||||
})
|
||||
if !reflect.DeepEqual(r, rang(10)) {
|
||||
t.Error("RangeItem error", r, rang(10))
|
||||
}
|
||||
|
||||
r = r[:0]
|
||||
zs.RangeByScore(func(i Item) bool {
|
||||
return i.(TestRank).score >= 3
|
||||
}, func(i Item) bool {
|
||||
return i.(TestRank).score <= 5
|
||||
}, false, func(i Item, rank int) bool {
|
||||
r = append(r, i)
|
||||
return true
|
||||
})
|
||||
var expect []Item
|
||||
for i := 3; i <= 5; i++ {
|
||||
expect = append(expect, TestRank{
|
||||
member: strconv.Itoa(i),
|
||||
score: i,
|
||||
})
|
||||
}
|
||||
if !reflect.DeepEqual(r, expect) {
|
||||
t.Error("RangeItem error", r, expect)
|
||||
}
|
||||
|
||||
r = r[:0]
|
||||
zs.RangeByScore(func(i Item) bool {
|
||||
return i.(TestRank).score >= 3
|
||||
}, func(i Item) bool {
|
||||
return i.(TestRank).score <= 5
|
||||
}, true, func(i Item, rank int) bool {
|
||||
r = append(r, i)
|
||||
return true
|
||||
})
|
||||
expect = expect[:0]
|
||||
for i := 5; i >= 3; i-- {
|
||||
expect = append(expect, TestRank{
|
||||
member: strconv.Itoa(i),
|
||||
score: i,
|
||||
})
|
||||
}
|
||||
if !reflect.DeepEqual(r, expect) {
|
||||
t.Error("RangeItem error", r, expect)
|
||||
}
|
||||
}
|
||||
|
||||
const benchmarkListSize = 10000
|
||||
|
||||
func BenchmarkAdd(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := perm(benchmarkListSize)
|
||||
b.StartTimer()
|
||||
i := 0
|
||||
for i < b.N {
|
||||
tr := New()
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.member, item)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAddIncrease(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := rang(benchmarkListSize)
|
||||
b.StartTimer()
|
||||
i := 0
|
||||
for i < b.N {
|
||||
tr := New()
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.Key(), item)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAddDecrease(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := revrang(benchmarkListSize, benchmarkListSize)
|
||||
b.StartTimer()
|
||||
i := 0
|
||||
for i < b.N {
|
||||
tr := New()
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.member, item)
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRemoveAdd(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := perm(benchmarkListSize)
|
||||
tr := New()
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.member, item)
|
||||
}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr.Remove(insertP[i%benchmarkListSize].Key())
|
||||
item := insertP[i%benchmarkListSize]
|
||||
tr.Add(item.member, item)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRemove(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := perm(benchmarkListSize)
|
||||
removeP := perm(benchmarkListSize)
|
||||
b.StartTimer()
|
||||
i := 0
|
||||
for i < b.N {
|
||||
b.StopTimer()
|
||||
tr := New()
|
||||
for _, v := range insertP {
|
||||
tr.Add(v.member, v)
|
||||
}
|
||||
b.StartTimer()
|
||||
for _, item := range removeP {
|
||||
tr.Remove(item.Key())
|
||||
i++
|
||||
if i >= b.N {
|
||||
return
|
||||
}
|
||||
}
|
||||
if tr.Length() > 0 {
|
||||
b.Error(tr.Length())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRank(b *testing.B) {
|
||||
b.StopTimer()
|
||||
insertP := perm(benchmarkListSize)
|
||||
tr := New()
|
||||
for _, v := range insertP {
|
||||
tr.Add(v.member, v)
|
||||
}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr.Rank(insertP[i%benchmarkListSize].Key(), true)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRange(b *testing.B) {
|
||||
insertP := perm(benchmarkListSize)
|
||||
tr := New()
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.member, item)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr.Range(0, 100, true, func(i Item, rank int) bool {
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRangeIterator(b *testing.B) {
|
||||
insertP := perm(benchmarkListSize)
|
||||
tr := New()
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.member, item)
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
it := tr.RangeIterator(0, 100, true)
|
||||
for ; it.Valid(); it.Next() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRangeItem(b *testing.B) {
|
||||
insertP := perm(benchmarkListSize)
|
||||
tr := New()
|
||||
for _, item := range insertP {
|
||||
tr.Add(item.member, item)
|
||||
}
|
||||
minScore, maxScore := 0, 100
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tr.RangeByScore(func(i Item) bool {
|
||||
return i.(TestRank).score >= minScore
|
||||
}, func(i Item) bool {
|
||||
return i.(TestRank).score <= maxScore
|
||||
}, true, func(i Item, rank int) bool {
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
1
go.work
1
go.work
@@ -20,6 +20,7 @@ use (
|
||||
./common/utils/sturc
|
||||
./common/utils/timer
|
||||
./common/utils/xml
|
||||
./common/utils/zset
|
||||
./logic
|
||||
./login
|
||||
./modules
|
||||
|
||||
@@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/modules/player/model"
|
||||
|
||||
"blazing/logic/service/fight"
|
||||
"blazing/logic/service/fight/info"
|
||||
@@ -40,7 +41,7 @@ func (h Controller) Escape(data *fight.EscapeFightInboundInfo, c *player.Player)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go c.FightC.Over(c, info.BattleOverReason.PlayerEscape)
|
||||
go c.FightC.Over(c, model.BattleOverReason.PlayerEscape)
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ func (Controller) PlayerFightBoss(data1 *fight.ChallengeBossInboundInfo, p *play
|
||||
}
|
||||
|
||||
ai.Prop[0] = 2
|
||||
fight.NewFight(p, ai, func(foi info.FightOverInfo) {
|
||||
fight.NewFight(p, ai, func(foi model.FightOverInfo) {
|
||||
if mdata.WinBonusID != 0 {
|
||||
if foi.Reason == 0 && foi.WinnerId == p.Info.UserID {
|
||||
p.SptCompletedTask(mdata.WinBonusID, 1)
|
||||
@@ -159,7 +159,7 @@ func (Controller) OnPlayerFightNpcMonster(data1 *fight.FightNpcMonsterInboundInf
|
||||
p.Fightinfo.Status = info.BattleMode.FIGHT_WITH_NPC //打野怪
|
||||
p.Fightinfo.Mode = info.BattleMode.MULTI_MODE //多人模式
|
||||
|
||||
fight.NewFight(p, ai, func(foi info.FightOverInfo) {
|
||||
fight.NewFight(p, ai, func(foi model.FightOverInfo) {
|
||||
//p.Done.Exec(model.MilestoneMode.Moster, []uint32{p.Info.MapID, monsterInfo.PetList[0].ID, uint32(foi.Reason)}, nil)
|
||||
if foi.Reason == 0 && foi.WinnerId == p.Info.UserID && p.CanGet() {
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/modules/player/model"
|
||||
|
||||
"blazing/logic/service/common"
|
||||
"blazing/logic/service/fight"
|
||||
@@ -17,7 +18,7 @@ func (h Controller) PetMelee(data *fight.StartPetWarInboundInfo, c *player.Playe
|
||||
c.Fightinfo.Status = info.BattleMode.PET_MELEE
|
||||
|
||||
err = c.JoinFight(func(p common.PlayerI) bool {
|
||||
_, err = fight.NewFight(p, c, func(foi info.FightOverInfo) {
|
||||
_, err = fight.NewFight(p, c, func(foi model.FightOverInfo) {
|
||||
if foi.Reason == 0 { //我放获胜
|
||||
|
||||
if foi.WinnerId == c.GetInfo().UserID {
|
||||
@@ -32,7 +33,7 @@ func (h Controller) PetMelee(data *fight.StartPetWarInboundInfo, c *player.Playe
|
||||
}
|
||||
|
||||
}
|
||||
if foi.Reason == info.BattleOverReason.PlayerOffline {
|
||||
if foi.Reason == model.BattleOverReason.PlayerOffline {
|
||||
if foi.WinnerId == c.GetInfo().UserID {
|
||||
|
||||
p.MessWin(false)
|
||||
@@ -71,7 +72,7 @@ func (h Controller) PetKing(data *fight.PetKingJoinInboundInfo, c *player.Player
|
||||
c.Fightinfo.FightType = data.FightType
|
||||
}
|
||||
err = c.JoinFight(func(p common.PlayerI) bool {
|
||||
_, err = fight.NewFight(p, c, func(foi info.FightOverInfo) {
|
||||
_, err = fight.NewFight(p, c, func(foi model.FightOverInfo) {
|
||||
if foi.Reason == 0 { //我放获胜
|
||||
switch data.Type {
|
||||
case 11:
|
||||
|
||||
@@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/modules/player/model"
|
||||
"sync/atomic"
|
||||
|
||||
"blazing/logic/service/common"
|
||||
@@ -54,7 +55,7 @@ func (h Controller) OnPlayerHandleFightInvite(data *fight.HandleFightInviteInbou
|
||||
return
|
||||
}
|
||||
|
||||
_, err = fight.NewFight(v, c, func(foi info.FightOverInfo) {
|
||||
_, err = fight.NewFight(v, c, func(foi model.FightOverInfo) {
|
||||
|
||||
//println("好友对战测试", foi.Reason)
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ func (h Controller) PetTawor(data *fight.StartTwarInboundInfo, c *player.Player)
|
||||
}
|
||||
|
||||
ai := player.NewAI_player(monsterInfo)
|
||||
_, err = fight.NewFight(c, ai, func(foi fightinfo.FightOverInfo) {
|
||||
_, err = fight.NewFight(c, ai, func(foi model.FightOverInfo) {
|
||||
if foi.Reason == 0 && foi.WinnerId == c.Info.UserID { //我放获胜
|
||||
switch data.Head.CMD {
|
||||
case 2429: //试炼之塔
|
||||
|
||||
@@ -5,11 +5,8 @@ import (
|
||||
"blazing/cool"
|
||||
"blazing/logic/service/common"
|
||||
"blazing/logic/service/fight"
|
||||
"blazing/logic/service/fight/top/repo"
|
||||
"blazing/logic/service/player"
|
||||
"context"
|
||||
|
||||
goredis "github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// 表示"宠物王加入"的入站消息数据
|
||||
@@ -20,11 +17,10 @@ type PetTOPLEVELnboundInfo struct {
|
||||
}
|
||||
|
||||
func (h Controller) JoINtop(data *PetTOPLEVELnboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
|
||||
cool.RedisDo(context.TODO(), "sun:join:2458", data.Head.UserID, data.Mode)
|
||||
client := cool.Redis.Client()
|
||||
cool.RedisDo(context.TODO(), "sun:join:2458", data.Head.UserID)
|
||||
|
||||
// 类型断言为 UniversalClient
|
||||
universalClient, _ := client.(goredis.UniversalClient)
|
||||
repo.NewPlayerRepository(universalClient).AddPlayerToPool(context.TODO(), data.Head.UserID, 1)
|
||||
// // 类型断言为 UniversalClient
|
||||
// universalClient, _ := client.(goredis.UniversalClient)
|
||||
// repo.NewPlayerRepository(universalClient).AddPlayerToPool(context.TODO(), data.Head.UserID, 1)
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package controller
|
||||
import (
|
||||
"blazing/common/data"
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/modules/player/model"
|
||||
"sync/atomic"
|
||||
|
||||
"blazing/logic/service/fight"
|
||||
@@ -66,7 +67,7 @@ func (h Controller) ArenaFightOwner(data1 *fight.ARENA_FIGHT_OWENR, c *player.Pl
|
||||
c.Fightinfo.Mode = info.BattleMode.SINGLE_MODE
|
||||
c.Fightinfo.Status = info.BattleMode.FIGHT_ARENA
|
||||
|
||||
_, err = fight.NewFight(c, c.GetSpace().Owner.ARENA_Player, func(foi info.FightOverInfo) { //我方邀请擂主挑战,我方先手
|
||||
_, err = fight.NewFight(c, c.GetSpace().Owner.ARENA_Player, func(foi model.FightOverInfo) { //我方邀请擂主挑战,我方先手
|
||||
|
||||
if foi.Reason != 0 && foi.WinnerId == c.GetInfo().UserID { //异常退出
|
||||
|
||||
|
||||
@@ -2,14 +2,15 @@ package common
|
||||
|
||||
import (
|
||||
"blazing/logic/service/fight/info"
|
||||
"blazing/modules/player/model"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
type FightI interface {
|
||||
Over(c PlayerI, id info.EnumBattleOverReason) //逃跑
|
||||
UseSkill(c PlayerI, id uint32) //使用技能
|
||||
GetCurrPET(c PlayerI) *info.BattlePetEntity //当前精灵
|
||||
GetOverInfo() info.FightOverInfo
|
||||
Over(c PlayerI, id model.EnumBattleOverReason) //逃跑
|
||||
UseSkill(c PlayerI, id uint32) //使用技能
|
||||
GetCurrPET(c PlayerI) *info.BattlePetEntity //当前精灵
|
||||
GetOverInfo() model.FightOverInfo
|
||||
Ownerid() uint32
|
||||
ReadyFight(c PlayerI) //是否准备战斗
|
||||
ChangePet(c PlayerI, id uint32)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"blazing/logic/service/fight/action"
|
||||
"blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/fight/input"
|
||||
"blazing/modules/player/model"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
@@ -26,12 +27,12 @@ func (*FightC) Compare(a, b action.BattleActionI) (action.BattleActionI, action.
|
||||
}
|
||||
|
||||
// 玩家逃跑/无响应/掉线
|
||||
func (f *FightC) Over(c common.PlayerI, res info.EnumBattleOverReason) {
|
||||
func (f *FightC) Over(c common.PlayerI, res model.EnumBattleOverReason) {
|
||||
if f.closefight {
|
||||
|
||||
return
|
||||
}
|
||||
if f.Info.Status != info.BattleMode.FIGHT_WITH_NPC && res == info.BattleOverReason.PlayerEscape {
|
||||
if f.Info.Status != info.BattleMode.FIGHT_WITH_NPC && res == model.BattleOverReason.PlayerEscape {
|
||||
return
|
||||
}
|
||||
// case *action.EscapeAction:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package effect
|
||||
|
||||
import (
|
||||
"blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/fight/input"
|
||||
"blazing/modules/player/model"
|
||||
)
|
||||
|
||||
// 13. n 回合逃跑;(a1: n, a2: 不逃跑精灵ID, a3/a4: 不逃跑系精灵)
|
||||
@@ -17,7 +17,7 @@ func (e *NewSel13) HookAction() bool {
|
||||
}
|
||||
r := e.Ctx().Our.FightC.GetOverInfo()
|
||||
if r.Round == uint32(e.Args()[0].IntPart()) {
|
||||
e.Ctx().Our.FightC.Over(e.Ctx().Our.Player, info.BattleOverReason.PlayerEscape)
|
||||
e.Ctx().Our.FightC.Over(e.Ctx().Our.Player, model.BattleOverReason.PlayerEscape)
|
||||
return false //阻止技能释放
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/fight/input"
|
||||
"blazing/logic/service/fight/node"
|
||||
"blazing/modules/player/model"
|
||||
|
||||
"github.com/alpacahq/alpacadecimal"
|
||||
)
|
||||
@@ -17,7 +18,7 @@ type EffectDefeatTrigger struct {
|
||||
can bool // 标记技能是否生效(当次攻击有效)
|
||||
effectID int // 效果ID,用于区分不同触发行为
|
||||
isd bool
|
||||
info info.AttackValue
|
||||
info model.AttackValue
|
||||
}
|
||||
|
||||
// 工厂函数:创建"击败触发"效果实例,传入效果ID
|
||||
@@ -95,7 +96,7 @@ func (e *EffectDefeatTrigger) SetArgs(t *input.Input, a ...int) {
|
||||
// -----------------------------------------------------------
|
||||
// 根据effectID触发对应行为
|
||||
// -----------------------------------------------------------
|
||||
func (e *EffectDefeatTrigger) triggerByID(at info.AttackValue) {
|
||||
func (e *EffectDefeatTrigger) triggerByID(at model.AttackValue) {
|
||||
switch e.effectID {
|
||||
case 66:
|
||||
e.triggerHealSelfOnDefeat(at)
|
||||
@@ -115,7 +116,7 @@ func (e *EffectDefeatTrigger) triggerByID(at info.AttackValue) {
|
||||
// -----------------------------------------------------------
|
||||
|
||||
// triggerHealSelfOnDefeat:击败对方后,恢复自身最大体力的1/n(对应Effect66)
|
||||
func (e *EffectDefeatTrigger) triggerHealSelfOnDefeat(_ info.AttackValue) {
|
||||
func (e *EffectDefeatTrigger) triggerHealSelfOnDefeat(_ model.AttackValue) {
|
||||
// 计算恢复量:自身最大体力 / n(n=SideEffectArgs[0])
|
||||
maxHP := e.Ctx().Our.CurrentPet.Info.MaxHp
|
||||
healAmount := alpacadecimal.NewFromInt(int64(maxHP)).Div(alpacadecimal.NewFromInt(int64(e.SideEffectArgs[0])))
|
||||
@@ -124,7 +125,7 @@ func (e *EffectDefeatTrigger) triggerHealSelfOnDefeat(_ info.AttackValue) {
|
||||
}
|
||||
|
||||
// triggerReduceNextHPOnDefeat:击败对方后,减少对方下次出战精灵最大体力的1/n(对应Effect67)
|
||||
func (e *EffectDefeatTrigger) triggerReduceNextHPOnDefeat(_ info.AttackValue) {
|
||||
func (e *EffectDefeatTrigger) triggerReduceNextHPOnDefeat(_ model.AttackValue) {
|
||||
// 计算伤害量:对方下只精灵最大体力 / n(n=SideEffectArgs[0])
|
||||
nextMaxHP := e.Ctx().Opp.CurrentPet.Info.MaxHp // 假设CurrentPet为下次出战精灵
|
||||
damageAmount := alpacadecimal.NewFromInt(int64(nextMaxHP)).Div(alpacadecimal.NewFromInt(int64(e.SideEffectArgs[0])))
|
||||
@@ -140,7 +141,7 @@ func (e *EffectDefeatTrigger) triggerReduceNextHPOnDefeat(_ info.AttackValue) {
|
||||
// SideEffectArgs[0] = m(触发概率,如30=30%)
|
||||
// SideEffectArgs[1] = XX等级类型(如1=攻击等级,对应info.LevelType枚举)
|
||||
// SideEffectArgs[2] = n(提升的等级值,如1=+1级)
|
||||
func (e *EffectDefeatTrigger) triggerLevelUpOnDefeat(_ info.AttackValue) {
|
||||
func (e *EffectDefeatTrigger) triggerLevelUpOnDefeat(_ model.AttackValue) {
|
||||
// 1. 检查参数是否足够
|
||||
if len(e.SideEffectArgs) < 3 {
|
||||
return
|
||||
@@ -162,7 +163,7 @@ func (e *EffectDefeatTrigger) triggerLevelUpOnDefeat(_ info.AttackValue) {
|
||||
// SideEffectArgs[0] = 目标对手类型(如info.PetType.Fire表示火系)
|
||||
// SideEffectArgs[1] = 要施加的状态(如info.PetStatus.Burned表示烧伤)
|
||||
// SideEffectArgs[2] = 状态持续回合
|
||||
func (e *EffectDefeatTrigger) triggerNextEnemyStatusOnDefeat(at info.AttackValue) {
|
||||
func (e *EffectDefeatTrigger) triggerNextEnemyStatusOnDefeat(at model.AttackValue) {
|
||||
// 这里补充原逻辑中状态施加的完整判断(如检查对手类型是否匹配)
|
||||
// 简化示例:直接处理状态施加
|
||||
for i, v := range at.Status {
|
||||
@@ -177,7 +178,7 @@ func (e *EffectDefeatTrigger) triggerNextEnemyStatusOnDefeat(at info.AttackValue
|
||||
}
|
||||
|
||||
// triggerTransferBoostsOnDefeat:击败对手后,复制其所有能力提升效果到自身(对应Effect421)
|
||||
func (e *EffectDefeatTrigger) triggerTransferBoostsOnDefeat(at info.AttackValue) {
|
||||
func (e *EffectDefeatTrigger) triggerTransferBoostsOnDefeat(at model.AttackValue) {
|
||||
// 复制被击败对手的能力提升
|
||||
for i, v := range at.Prop {
|
||||
if v > 0 {
|
||||
|
||||
@@ -90,18 +90,6 @@ type DefaultEndData struct {
|
||||
Reason string
|
||||
}
|
||||
|
||||
// 战斗结束原因枚举
|
||||
type EnumBattleOverReason int
|
||||
|
||||
var BattleOverReason = enum.New[struct {
|
||||
PlayerOffline EnumBattleOverReason `enum:"1"` //掉线
|
||||
PlayerOVerTime EnumBattleOverReason `enum:"2"` //超时
|
||||
NOTwind EnumBattleOverReason `enum:"3"` //打成平手
|
||||
DefaultEnd EnumBattleOverReason `enum:"4"` //默认结束
|
||||
PlayerEscape EnumBattleOverReason `enum:"5"` //逃跑
|
||||
Cacthok EnumBattleOverReason `enum:"6"`
|
||||
}]()
|
||||
|
||||
// 战斗模式
|
||||
|
||||
var Playerinvitemap map[uint32][]Playerinvite = make(map[uint32][]Playerinvite) //玩家邀请信息 ,比如一个玩家被多人邀请对战
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"blazing/common/data"
|
||||
"blazing/modules/player/model"
|
||||
|
||||
"github.com/tnnmigga/enum"
|
||||
@@ -80,12 +79,12 @@ type FightPetInfo struct {
|
||||
Prop [6]int8
|
||||
}
|
||||
type AttackValueS struct {
|
||||
FAttack AttackValue
|
||||
SAttack AttackValue
|
||||
FAttack model.AttackValue
|
||||
SAttack model.AttackValue
|
||||
}
|
||||
|
||||
func NewAttackValue(userid uint32) *AttackValue {
|
||||
return &AttackValue{
|
||||
func NewAttackValue(userid uint32) *model.AttackValue {
|
||||
return &model.AttackValue{
|
||||
userid,
|
||||
0,
|
||||
0,
|
||||
@@ -103,28 +102,6 @@ func NewAttackValue(userid uint32) *AttackValue {
|
||||
}
|
||||
}
|
||||
|
||||
// AttackValue 战斗中的攻击数值信息
|
||||
type AttackValue struct {
|
||||
UserID uint32 `json:"userId" fieldDescription:"玩家的米米号 与野怪对战userid = 0"`
|
||||
SkillID uint32 `json:"skillId" fieldDescription:"使用技能的id"`
|
||||
AttackTime uint32 `json:"attackTime" fieldDescription:"是否击中 如果为0 则miss 如果为1 则击中,2为必中"`
|
||||
LostHp uint32 `json:"lostHp" fieldDescription:"我方造成的伤害"`
|
||||
GainHp int32 `json:"gainHp" fieldDescription:"我方获得血量"`
|
||||
RemainHp int32 `json:"remainHp" fieldDescription:"我方剩余血量"`
|
||||
MaxHp uint32 `json:"maxHp" fieldDescription:"我方最大血量"`
|
||||
//颜色
|
||||
State uint32 `json:"state" `
|
||||
SkillListLen uint32 `struc:"sizeof=SkillList"`
|
||||
SkillList []model.SkillInfo `json:"skillList" fieldDescription:"根据精灵的数据插入技能 最多4条 不定长"`
|
||||
IsCritical uint32 `json:"isCritical" fieldDescription:"是否暴击"`
|
||||
Status [20]int8 //精灵的状态
|
||||
// 攻击,防御,特供,特防,速度,命中
|
||||
Prop [6]int8
|
||||
Offensive float32
|
||||
// OwnerMaxShield uint32 `json:"ownerMaxShield" fieldDescription:"我方最大护盾"`
|
||||
// OwnerCurrentShield uint32 `json:"ownerCurrentShield" fieldDescription:"我方当前护盾"`
|
||||
}
|
||||
|
||||
type WeakenedS struct {
|
||||
Stack int8 `struc:"skip"`
|
||||
Round int8
|
||||
@@ -203,8 +180,8 @@ type PropDict struct {
|
||||
|
||||
// NoteUseSkillOutboundInfo 战斗技能使用通知的出站信息结构体
|
||||
type NoteUseSkillOutboundInfo struct {
|
||||
FirstAttackInfo AttackValue // 本轮先手的精灵在释放技能结束后的状态
|
||||
SecondAttackInfo AttackValue // 本轮后手的精灵在释放技能结束后的状态
|
||||
FirstAttackInfo model.AttackValue // 本轮先手的精灵在释放技能结束后的状态
|
||||
SecondAttackInfo model.AttackValue // 本轮后手的精灵在释放技能结束后的状态
|
||||
}
|
||||
|
||||
type FightStartOutboundInfo struct {
|
||||
@@ -218,14 +195,6 @@ type FightStartOutboundInfo struct {
|
||||
Info2 FightPetInfo `fieldDesc:"当前战斗精灵的信息 可能不准.看前端代码是以userid来判断哪个结构体是我方的" serialize:"struct"`
|
||||
}
|
||||
|
||||
type FightUserInfo struct {
|
||||
// 用户ID(野怪为0),@UInt long
|
||||
UserID uint32 `fieldDesc:"userID 如果为野怪则为0" `
|
||||
|
||||
// 玩家名称(野怪为UTF-8的'-',固定16字节)
|
||||
// 使用[16]byte存储固定长度的字节数组
|
||||
Nick string `struc:"[16]byte"`
|
||||
}
|
||||
type S2C_2404 struct {
|
||||
// 用户ID(野怪为0),@UInt long
|
||||
UserID uint32 `fieldDesc:"userID 如果为野怪则为0" `
|
||||
@@ -235,86 +204,7 @@ type S2C_50005 struct {
|
||||
Title uint32
|
||||
}
|
||||
|
||||
// NoteReadyToFightInfo 战斗准备就绪消息结构体,NoteReadyToFightInfo
|
||||
type NoteReadyToFightInfo struct {
|
||||
//MAXPET uint32 `struc:"skip"` // 最大精灵数 struc:"skip"`
|
||||
// 战斗类型ID(与野怪战斗为3,与人战斗为1,前端似乎未使用)
|
||||
// @UInt long
|
||||
Status uint32 `fieldDesc:"战斗类型ID 但前端好像没有用到 与野怪战斗为3,与人战斗似乎是1" `
|
||||
//Mode uint32 `struc:"skip"`
|
||||
// 我方信息
|
||||
OurInfo FightUserInfo `fieldDesc:"我方信息" serialize:"struct"`
|
||||
// Our *socket.Player `struc:"skip"`
|
||||
OurPetListLen uint32 `struc:"sizeof=OurPetList"`
|
||||
// 我方携带精灵的信息
|
||||
// ArrayList<ReadyFightPetInfo>,使用切片模拟动态列表
|
||||
OurPetList []ReadyFightPetInfo `fieldDesc:"我方携带精灵的信息" serialize:"lengthFirst,lengthType=uint16,type=structArray"`
|
||||
|
||||
// 对方信息
|
||||
OpponentInfo FightUserInfo `fieldDesc:"对方信息" serialize:"struct"`
|
||||
//Opp *socket.Player `struc:"skip"`
|
||||
OpponentPetListLen uint32 `struc:"sizeof=OpponentPetList"`
|
||||
// 敌方的精灵信息
|
||||
// 野怪战斗时:客户端接收此包前已生成精灵PetInfo,将部分信息写入该列表
|
||||
OpponentPetList []ReadyFightPetInfo `fieldDesc:"敌方的精灵信息 如果是野怪 那么再给客户端发送这个包体时就提前生成好了这只精灵的PetInfo,然后把从PetInfo中把部分信息写入到这个敌方的精灵信息中再发送这个包结构体" serialize:"lengthFirst,lengthType=uint16,type=structArray"`
|
||||
}
|
||||
|
||||
// ReadyFightPetInfo 准备战斗的精灵信息结构体,ReadyFightPetInfo类
|
||||
type ReadyFightPetInfo struct {
|
||||
// 精灵ID,@UInt long
|
||||
ID uint32 `fieldDesc:"精灵ID" `
|
||||
|
||||
// 精灵等级,@UInt long
|
||||
Level uint32 `fieldDesc:"精灵等级" `
|
||||
|
||||
// 精灵当前HP,@UInt long
|
||||
Hp uint32 `fieldDesc:"精灵HP" `
|
||||
|
||||
// 精灵最大HP,@UInt long
|
||||
MaxHp uint32 `fieldDesc:"最大HP" `
|
||||
SkillListLen uint32 `struc:"sizeof=SkillList"`
|
||||
// 技能信息列表(固定4个元素,技能ID和剩余PP,无技能则为0)
|
||||
// List<SkillInfo>,初始化容量为4
|
||||
SkillList []model.SkillInfo `fieldDesc:"技能信息 技能ID跟剩余PP 固定32字节 没有给0" serialize:"fixedLength=4,type=structArray"`
|
||||
|
||||
// 精灵捕获时间,@UInt long
|
||||
CatchTime uint32 `fieldDesc:"精灵捕获时间" `
|
||||
|
||||
// 捕捉地图(固定给0),@UInt long
|
||||
CatchMap uint32 `fieldDesc:"捕捉地图 给0" `
|
||||
|
||||
// 固定给0,@UInt long
|
||||
CatchRect uint32 `fieldDesc:"给0" `
|
||||
|
||||
// 固定给0,@UInt long
|
||||
CatchLevel uint32 `fieldDesc:"给0" `
|
||||
SkinID uint32 `fieldDesc:"精灵皮肤ID" `
|
||||
// 是否闪光(@UInt long → uint32,0=否,1=是)
|
||||
ShinyLen uint32 `json:"-" struc:"sizeof=ShinyInfo"`
|
||||
ShinyInfo []data.GlowFilter `json:"ShinyInfo,omitempty"`
|
||||
}
|
||||
|
||||
// FightOverInfo 战斗结束信息结构体 2506
|
||||
type FightOverInfo struct {
|
||||
//0 正常结束
|
||||
//1=isPlayerLost 对方玩家退出
|
||||
// 2=isOvertime 超时
|
||||
// 3=isDraw 双方平手
|
||||
// 4=isSysError 系统错误
|
||||
// 5=isNpcEscape 精灵主动逃跑
|
||||
|
||||
Winpet *model.PetInfo `struc:"skip"`
|
||||
Round uint32 `struc:"skip"`
|
||||
LastAttavue AttackValue `struc:"skip"`
|
||||
//7 切磋结束
|
||||
Reason EnumBattleOverReason // 固定值0
|
||||
WinnerId uint32 // 胜者的米米号 野怪为0
|
||||
TwoTimes uint32 // 双倍经验剩余次数
|
||||
ThreeTimes uint32 // 三倍经验剩余次数
|
||||
AutoFightTimes uint32 // 自动战斗剩余次数
|
||||
EnergyTime uint32 // 能量吸收器剩余次数
|
||||
LearnTimes uint32 // 双倍学习器剩余次数
|
||||
}
|
||||
|
||||
type CatchMonsterOutboundInfo struct {
|
||||
// CatchTime 捕捉时间
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
type FightC struct {
|
||||
//准备战斗信息
|
||||
ReadyInfo info.NoteReadyToFightInfo
|
||||
ReadyInfo model.NoteReadyToFightInfo
|
||||
//开始战斗信息
|
||||
info.FightStartOutboundInfo
|
||||
Info info.Fightinfo
|
||||
@@ -47,9 +47,9 @@ type FightC struct {
|
||||
closefight bool
|
||||
overl sync.Once
|
||||
waittime int
|
||||
info.FightOverInfo
|
||||
model.FightOverInfo
|
||||
//战斗结束的插装
|
||||
callback func(info.FightOverInfo)
|
||||
callback func(model.FightOverInfo)
|
||||
}
|
||||
|
||||
func (f *FightC) Ownerid() uint32 {
|
||||
@@ -250,9 +250,9 @@ func RandomElfIDs(n int) []int {
|
||||
|
||||
return ids
|
||||
}
|
||||
func initfightready(in *input.Input) (info.FightUserInfo, []info.ReadyFightPetInfo) {
|
||||
t := make([]info.ReadyFightPetInfo, len(in.AllPet))
|
||||
userindo := info.FightUserInfo{
|
||||
func initfightready(in *input.Input) (model.FightUserInfo, []model.ReadyFightPetInfo) {
|
||||
t := make([]model.ReadyFightPetInfo, len(in.AllPet))
|
||||
userindo := model.FightUserInfo{
|
||||
UserID: in.UserID,
|
||||
Nick: in.Player.GetInfo().Nick,
|
||||
}
|
||||
@@ -291,7 +291,7 @@ func (f *FightC) GetOverChan() chan struct{} {
|
||||
return f.over
|
||||
|
||||
}
|
||||
func (f *FightC) GetOverInfo() info.FightOverInfo {
|
||||
func (f *FightC) GetOverInfo() model.FightOverInfo {
|
||||
return f.FightOverInfo
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package input
|
||||
import (
|
||||
"blazing/common/data/xmlres"
|
||||
"blazing/cool"
|
||||
"blazing/modules/player/model"
|
||||
"fmt"
|
||||
|
||||
"blazing/logic/service/common"
|
||||
@@ -23,7 +24,7 @@ type Input struct {
|
||||
Opp *Input
|
||||
CanCapture int
|
||||
Finished bool //是否加载完成
|
||||
*info.AttackValue
|
||||
*model.AttackValue
|
||||
FightC common.FightI
|
||||
// info.BattleActionI
|
||||
Effects []Effect //effects 实际上全局就是effect无限回合 //effects容器 技能的
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"blazing/common/socket/errorcode"
|
||||
"blazing/common/utils"
|
||||
"blazing/cool"
|
||||
"blazing/modules/player/model"
|
||||
"context"
|
||||
"sync/atomic"
|
||||
|
||||
@@ -97,7 +98,7 @@ func (f *FightC) battleLoop() {
|
||||
})
|
||||
if f.Info.Status == info.BattleMode.FIGHT_WITH_NPC {
|
||||
addpet := f.Opp.Player.GetInfo().PetList[0]
|
||||
if f.Reason == info.BattleOverReason.Cacthok {
|
||||
if f.Reason == model.BattleOverReason.Cacthok {
|
||||
f.WinnerId = f.ownerID
|
||||
|
||||
addpet.EffectInfo = nil //清空特性信息
|
||||
@@ -280,7 +281,7 @@ func (f *FightC) handleTimeout(ourID, oppID uint32, actions map[uint32]action.Ba
|
||||
}
|
||||
//获胜方是已经出招的
|
||||
f.WinnerId = f.GetInputByPlayer(f.getPlayerByID(pid), false).Player.GetInfo().UserID
|
||||
f.Reason = info.BattleOverReason.PlayerOVerTime
|
||||
f.Reason = model.BattleOverReason.PlayerOVerTime
|
||||
f.closefight = true
|
||||
return true
|
||||
}
|
||||
@@ -365,7 +366,7 @@ func (f *FightC) handleItemAction(a *action.UseItemAction) {
|
||||
ok, _ := f.Our.Capture(f.Opp.CurrentPet, a.ItemID, -1)
|
||||
our := f.Our.Player.(*player.Player)
|
||||
if ok {
|
||||
f.Reason = info.BattleOverReason.Cacthok
|
||||
f.Reason = model.BattleOverReason.Cacthok
|
||||
f.closefight = true
|
||||
} else {
|
||||
our.SendPack(common.NewTomeeHeader(2409, f.ownerID).Pack(&info.CatchMonsterOutboundInfo{}))
|
||||
|
||||
@@ -9,12 +9,13 @@ import (
|
||||
"blazing/logic/service/fight/input"
|
||||
"blazing/logic/service/player"
|
||||
"blazing/modules/config/service"
|
||||
"blazing/modules/player/model"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 创建新战斗,邀请方和被邀请方,或者玩家和野怪方
|
||||
func NewFight(p1, p2 common.PlayerI, fn func(info.FightOverInfo)) (*FightC, errorcode.ErrorCode) {
|
||||
func NewFight(p1, p2 common.PlayerI, fn func(model.FightOverInfo)) (*FightC, errorcode.ErrorCode) {
|
||||
|
||||
// fmt.Println("NewFight", p1.GetInfo().UserID)
|
||||
f := &FightC{}
|
||||
@@ -84,7 +85,7 @@ func NewFight(p1, p2 common.PlayerI, fn func(info.FightOverInfo)) (*FightC, erro
|
||||
//fmt.Println(f.Our.UserID, "战斗超时结算")
|
||||
if !f.Our.Finished || !f.Opp.Finished { //如果有任一没有加载完成
|
||||
f.closefight = true //阻止继续添加action
|
||||
f.Reason = info.BattleOverReason.PlayerOffline
|
||||
f.Reason = model.BattleOverReason.PlayerOffline
|
||||
switch {
|
||||
case !f.Opp.Finished: //邀请方没加载完成 先判断邀请方,如果都没加载完成,就算做房主胜利
|
||||
f.WinnerId = f.Our.Player.GetInfo().UserID
|
||||
|
||||
@@ -3,9 +3,9 @@ package player
|
||||
import (
|
||||
"blazing/common/data/share"
|
||||
"blazing/cool"
|
||||
"blazing/modules/player/model"
|
||||
"fmt"
|
||||
|
||||
"blazing/logic/service/fight/info"
|
||||
"blazing/logic/service/space"
|
||||
"context"
|
||||
"time"
|
||||
@@ -43,7 +43,7 @@ func (p *Player) Save() {
|
||||
|
||||
}
|
||||
}()
|
||||
p.FightC.Over(p, info.BattleOverReason.PlayerOffline) //玩家逃跑,但是不能锁线程
|
||||
p.FightC.Over(p, model.BattleOverReason.PlayerOffline) //玩家逃跑,但是不能锁线程
|
||||
}()
|
||||
//<-ov
|
||||
select {
|
||||
|
||||
187
modules/player/model/pvp.go
Normal file
187
modules/player/model/pvp.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"blazing/common/data"
|
||||
"blazing/cool"
|
||||
|
||||
"github.com/tnnmigga/enum"
|
||||
)
|
||||
|
||||
// 表名常量
|
||||
const TableNamePlayerPVP = "player_pvp"
|
||||
|
||||
// PVP 对应数据库表 player_pvp,用于记录用户PVP赛季数据及场次统计
|
||||
type PVP struct {
|
||||
Base
|
||||
PlayerID uint64 `gorm:"not null;index:idx_pvp_player_id;comment:'所属玩家ID'" json:"player_id"`
|
||||
//SeasonID uint32 `gorm:"not null;index:idx_pvp_season_id;comment:'赛季ID(如2026S1=101)'" json:"season_id"`
|
||||
SeasonData []PVPRankInfo `gorm:"type:jsonb;not null;comment:'赛季核心数据'" json:"season_data"`
|
||||
MatchRecords []PVPMatchRecord `gorm:"type:jsonb;not null;comment:'本赛季场次记录(仅存最近100场)'" json:"match_records"`
|
||||
RankInfo PVPRankInfo `gorm:"type:jsonb;not null;comment:'本赛季排名信息'" json:"rank_info"`
|
||||
}
|
||||
|
||||
// PVPSeasonData PVP赛季核心统计数据
|
||||
// 聚合维度:总场次、胜负、积分、胜率等
|
||||
type PVPRankInfo struct {
|
||||
Rank uint32 `json:"rank"` // 本赛季全服排名(0=未上榜)
|
||||
Segment uint32 `json:"segment"` // 段位ID(如1=青铜 2=白银...)
|
||||
SegmentStar uint32 `json:"segment_star"` // 段位星级(如青铜3星)
|
||||
NextSegmentScore int32 `json:"next_segment_score"` // 升段所需积分
|
||||
TotalMatch uint32 `json:"total_match"` // 本赛季总场次
|
||||
WinMatch uint32 `json:"win_match"` // 本赛季胜利场次
|
||||
LoseMatch uint32 `json:"lose_match"` // 本赛季失败场次
|
||||
DrawMatch uint32 `json:"draw_match"` // 本赛季平局场次
|
||||
TotalScore int32 `json:"total_score"` // 本赛季总积分(胜加负减)
|
||||
HighestScore int32 `json:"highest_score"` // 本赛季最高积分
|
||||
ContinuousWin uint32 `json:"continuous_win"` // 本赛季最高连胜次数
|
||||
LastMatchTime uint64 `json:"last_match_time"` // 最后一场PVP时间(时间戳)
|
||||
}
|
||||
|
||||
// PVPMatchRecord PVP单场记录
|
||||
// 记录每一场的详细信息,便于复盘和统计
|
||||
type PVPMatchRecord struct {
|
||||
MatchID string `json:"match_id"` // 匹配局ID(全局唯一)
|
||||
MatchTime uint64 `json:"match_time"` // 对局时间(时间戳)
|
||||
NoteReadyToFightInfo
|
||||
Result uint32 `json:"result"` // 对局结果:0=负 1=胜 2=平
|
||||
ScoreChange int32 `json:"score_change"` // 本场积分变化(+10/-5等)
|
||||
UsedPetIDs []uint32 `json:"used_pet_ids"` // 本场使用的精灵ID列表
|
||||
WinStreak uint32 `json:"win_streak"` // 本场结束后的连胜数
|
||||
Duration uint32 `json:"duration"` // 对局时长(秒)
|
||||
}
|
||||
|
||||
// NoteReadyToFightInfo 战斗准备就绪消息结构体,NoteReadyToFightInfo
|
||||
type NoteReadyToFightInfo struct {
|
||||
//MAXPET uint32 `struc:"skip"` // 最大精灵数 struc:"skip"`
|
||||
// 战斗类型ID(与野怪战斗为3,与人战斗为1,前端似乎未使用)
|
||||
// @UInt long
|
||||
Status uint32 `fieldDesc:"战斗类型ID 但前端好像没有用到 与野怪战斗为3,与人战斗似乎是1" `
|
||||
//Mode uint32 `struc:"skip"`
|
||||
// 我方信息
|
||||
OurInfo FightUserInfo `fieldDesc:"我方信息" serialize:"struct"`
|
||||
// Our *socket.Player `struc:"skip"`
|
||||
OurPetListLen uint32 `struc:"sizeof=OurPetList"`
|
||||
// 我方携带精灵的信息
|
||||
// ArrayList<ReadyFightPetInfo>,使用切片模拟动态列表
|
||||
OurPetList []ReadyFightPetInfo `fieldDesc:"我方携带精灵的信息" serialize:"lengthFirst,lengthType=uint16,type=structArray"`
|
||||
|
||||
// 对方信息
|
||||
OpponentInfo FightUserInfo `fieldDesc:"对方信息" serialize:"struct"`
|
||||
//Opp *socket.Player `struc:"skip"`
|
||||
OpponentPetListLen uint32 `struc:"sizeof=OpponentPetList"`
|
||||
// 敌方的精灵信息
|
||||
// 野怪战斗时:客户端接收此包前已生成精灵PetInfo,将部分信息写入该列表
|
||||
OpponentPetList []ReadyFightPetInfo `fieldDesc:"敌方的精灵信息 如果是野怪 那么再给客户端发送这个包体时就提前生成好了这只精灵的PetInfo,然后把从PetInfo中把部分信息写入到这个敌方的精灵信息中再发送这个包结构体" serialize:"lengthFirst,lengthType=uint16,type=structArray"`
|
||||
}
|
||||
type FightUserInfo struct {
|
||||
// 用户ID(野怪为0),@UInt long
|
||||
UserID uint32 `fieldDesc:"userID 如果为野怪则为0" `
|
||||
|
||||
// 玩家名称(野怪为UTF-8的'-',固定16字节)
|
||||
// 使用[16]byte存储固定长度的字节数组
|
||||
Nick string `struc:"[16]byte"`
|
||||
}
|
||||
|
||||
// ReadyFightPetInfo 准备战斗的精灵信息结构体,ReadyFightPetInfo类
|
||||
type ReadyFightPetInfo struct {
|
||||
// 精灵ID,@UInt long
|
||||
ID uint32 `fieldDesc:"精灵ID" `
|
||||
|
||||
// 精灵等级,@UInt long
|
||||
Level uint32 `fieldDesc:"精灵等级" `
|
||||
|
||||
// 精灵当前HP,@UInt long
|
||||
Hp uint32 `fieldDesc:"精灵HP" `
|
||||
|
||||
// 精灵最大HP,@UInt long
|
||||
MaxHp uint32 `fieldDesc:"最大HP" `
|
||||
SkillListLen uint32 `struc:"sizeof=SkillList"`
|
||||
// 技能信息列表(固定4个元素,技能ID和剩余PP,无技能则为0)
|
||||
// List<SkillInfo>,初始化容量为4
|
||||
SkillList []SkillInfo `fieldDesc:"技能信息 技能ID跟剩余PP 固定32字节 没有给0" serialize:"fixedLength=4,type=structArray"`
|
||||
|
||||
// 精灵捕获时间,@UInt long
|
||||
CatchTime uint32 `fieldDesc:"精灵捕获时间" `
|
||||
|
||||
// 捕捉地图(固定给0),@UInt long
|
||||
CatchMap uint32 `fieldDesc:"捕捉地图 给0" `
|
||||
|
||||
// 固定给0,@UInt long
|
||||
CatchRect uint32 `fieldDesc:"给0" `
|
||||
|
||||
// 固定给0,@UInt long
|
||||
CatchLevel uint32 `fieldDesc:"给0" `
|
||||
SkinID uint32 `fieldDesc:"精灵皮肤ID" `
|
||||
// 是否闪光(@UInt long → uint32,0=否,1=是)
|
||||
ShinyLen uint32 `json:"-" struc:"sizeof=ShinyInfo"`
|
||||
ShinyInfo []data.GlowFilter `json:"ShinyInfo,omitempty"`
|
||||
}
|
||||
type FightOverInfo struct {
|
||||
//0 正常结束
|
||||
//1=isPlayerLost 对方玩家退出
|
||||
// 2=isOvertime 超时
|
||||
// 3=isDraw 双方平手
|
||||
// 4=isSysError 系统错误
|
||||
// 5=isNpcEscape 精灵主动逃跑
|
||||
|
||||
Winpet *PetInfo `struc:"skip"`
|
||||
Round uint32 `struc:"skip"`
|
||||
LastAttavue AttackValue `struc:"skip"`
|
||||
//7 切磋结束
|
||||
Reason EnumBattleOverReason // 固定值0
|
||||
WinnerId uint32 // 胜者的米米号 野怪为0
|
||||
TwoTimes uint32 // 双倍经验剩余次数
|
||||
ThreeTimes uint32 // 三倍经验剩余次数
|
||||
AutoFightTimes uint32 // 自动战斗剩余次数
|
||||
EnergyTime uint32 // 能量吸收器剩余次数
|
||||
LearnTimes uint32 // 双倍学习器剩余次数
|
||||
}
|
||||
|
||||
// 战斗结束原因枚举
|
||||
type EnumBattleOverReason int
|
||||
|
||||
var BattleOverReason = enum.New[struct {
|
||||
PlayerOffline EnumBattleOverReason `enum:"1"` //掉线
|
||||
PlayerOVerTime EnumBattleOverReason `enum:"2"` //超时
|
||||
NOTwind EnumBattleOverReason `enum:"3"` //打成平手
|
||||
DefaultEnd EnumBattleOverReason `enum:"4"` //默认结束
|
||||
PlayerEscape EnumBattleOverReason `enum:"5"` //逃跑
|
||||
Cacthok EnumBattleOverReason `enum:"6"`
|
||||
}]()
|
||||
|
||||
// AttackValue 战斗中的攻击数值信息
|
||||
type AttackValue struct {
|
||||
UserID uint32 `json:"userId" fieldDescription:"玩家的米米号 与野怪对战userid = 0"`
|
||||
SkillID uint32 `json:"skillId" fieldDescription:"使用技能的id"`
|
||||
AttackTime uint32 `json:"attackTime" fieldDescription:"是否击中 如果为0 则miss 如果为1 则击中,2为必中"`
|
||||
LostHp uint32 `json:"lostHp" fieldDescription:"我方造成的伤害"`
|
||||
GainHp int32 `json:"gainHp" fieldDescription:"我方获得血量"`
|
||||
RemainHp int32 `json:"remainHp" fieldDescription:"我方剩余血量"`
|
||||
MaxHp uint32 `json:"maxHp" fieldDescription:"我方最大血量"`
|
||||
//颜色
|
||||
State uint32 `json:"state" `
|
||||
SkillListLen uint32 `struc:"sizeof=SkillList"`
|
||||
SkillList []SkillInfo `json:"skillList" fieldDescription:"根据精灵的数据插入技能 最多4条 不定长"`
|
||||
IsCritical uint32 `json:"isCritical" fieldDescription:"是否暴击"`
|
||||
Status [20]int8 //精灵的状态
|
||||
// 攻击,防御,特供,特防,速度,命中
|
||||
Prop [6]int8
|
||||
Offensive float32
|
||||
// OwnerMaxShield uint32 `json:"ownerMaxShield" fieldDescription:"我方最大护盾"`
|
||||
// OwnerCurrentShield uint32 `json:"ownerCurrentShield" fieldDescription:"我方当前护盾"`
|
||||
}
|
||||
|
||||
// TableName 返回表名
|
||||
func (*PVP) TableName() string {
|
||||
return TableNamePlayerPVP
|
||||
}
|
||||
|
||||
// GroupName 返回表组名
|
||||
func (*PVP) GroupName() string {
|
||||
return "default"
|
||||
}
|
||||
|
||||
// init 程序启动时自动创建表
|
||||
func init() {
|
||||
cool.CreateTable(&PVP{})
|
||||
}
|
||||
Reference in New Issue
Block a user