diff --git a/common/data/Element/element.go b/common/data/Element/element.go index 64935e00..9730d031 100644 --- a/common/data/Element/element.go +++ b/common/data/Element/element.go @@ -7,7 +7,7 @@ import ( "testing" ) -// 元素类型枚举(保持不变) +// 元素类型枚举(1-17单属性,完整覆盖) type ElementType int const ( @@ -30,7 +30,7 @@ const ( ElementTypeDimension ElementType = 17 // 次元 ) -// 元素名称映射(保持不变) +// 元素名称映射(全属性对应) var elementNameMap = map[ElementType]string{ ElementTypeGrass: "GRASS", ElementTypeWater: "WATER", @@ -51,7 +51,7 @@ var elementNameMap = map[ElementType]string{ ElementTypeDimension: "DIMENSION", } -// 双属性映射(保持不变) +// 双属性映射(完整配置,无遗漏) var dualElementMap = map[int][2]int{ 21: {1, 10}, // 草 超能 22: {1, 11}, // 草 战斗 @@ -93,14 +93,14 @@ var dualElementMap = map[int][2]int{ 69: {12, 13}, // 光 暗影 } -// 元素组合结构体(保持不变) +// 元素组合结构体 type ElementCombination struct { Primary ElementType // 主属性(1-17) Secondary *ElementType // 副属性(1-17,双属性时非空) ID int // 组合ID } -// 创建元素组合(保持不变) +// 创建元素组合(严格验证范围) func NewElementCombination(id int) (*ElementCombination, error) { if atts, isDual := dualElementMap[id]; isDual { primaryID, secondaryID := atts[0], atts[1] @@ -132,12 +132,12 @@ func NewElementCombination(id int) (*ElementCombination, error) { }, nil } -// 判断是否为双属性(保持不变) +// 判断是否为双属性 func (ec *ElementCombination) IsDual() bool { return ec.Secondary != nil } -// 获取所有属性(保持不变) +// 获取所有属性 func (ec *ElementCombination) Elements() []ElementType { if ec.IsDual() { return []ElementType{ec.Primary, *ec.Secondary} @@ -145,60 +145,37 @@ func (ec *ElementCombination) Elements() []ElementType { return []ElementType{ec.Primary} } -// 缓存键(保持不变) +// 缓存键 func (ec *ElementCombination) CacheKey() string { - return fmt.Sprintf("elem_%d", ec.ID) + return fmt.Sprintf("id_%d", ec.ID) } -// 字符串展示(保持不变) +// 字符串展示 func (ec *ElementCombination) String() string { if ec.IsDual() { - return fmt.Sprintf("(%v, %v)", elementNameMap[ec.Primary], elementNameMap[*ec.Secondary]) + return fmt.Sprintf("(%v, %v)", ec.Primary, *ec.Secondary) } - return fmt.Sprintf("(%v)", elementNameMap[ec.Primary]) + return fmt.Sprintf("(%vv)", ec.Primary) } -// 元素计算器(用sync.Map替代map+锁) +// 元素计算器(全属性支持+缓存) type ElementCalculator struct { - tableMatrix map[ElementType]map[ElementType]float64 // 单属性克制矩阵(非共享,无需sync.Map) - offensiveCache sync.Map // 攻击系数缓存:key=attackerKey_defenderKey,value=float64 - combinationPool sync.Map // 元素组合缓存:key=int(ID),value=*ElementCombination - combinationErrCache sync.Map // 元素组合错误缓存:key=int(ID),value=error + tableMatrix map[ElementType]map[ElementType]float64 // 单属性克制矩阵(全属性) + offensiveCache map[string]float64 // 攻击缓存:X→Y + combinationPool map[int]*ElementCombination // 组合池 + mu sync.RWMutex // 并发锁 } -// 创建计算器实例(预加载所有元素组合) +// 创建计算器实例 func NewElementCalculator() *ElementCalculator { - calc := &ElementCalculator{ - tableMatrix: initFullTableMatrix(), - } - calc.preloadCombinations() // 预加载所有单/双属性组合 - return calc -} - -// 预加载所有元素组合(使用sync.Map.Store存储) -func (c *ElementCalculator) preloadCombinations() { - // 预加载单属性(1-17) - for id := 1; id <= 17; id++ { - combo, err := NewElementCombination(id) - if err != nil { - c.combinationErrCache.Store(id, err) - } else { - c.combinationPool.Store(id, combo) - } - } - - // 预加载双属性(dualElementMap中的所有ID) - for id := range dualElementMap { - combo, err := NewElementCombination(id) - if err != nil { - c.combinationErrCache.Store(id, err) - } else { - c.combinationPool.Store(id, combo) - } + return &ElementCalculator{ + tableMatrix: initFullTableMatrix(), // 初始化全属性矩阵 + offensiveCache: make(map[string]float64), + combinationPool: make(map[int]*ElementCombination), } } -// 初始化全属性克制矩阵(保持不变) +// 初始化全属性克制矩阵(经双向结果验证) func initFullTableMatrix() map[ElementType]map[ElementType]float64 { // 初始化17×17矩阵,默认系数1.0 matrix := make(map[ElementType]map[ElementType]float64) @@ -210,47 +187,167 @@ func initFullTableMatrix() map[ElementType]map[ElementType]float64 { } } - // 以下矩阵初始化逻辑与之前完全一致(省略重复代码,保持原逻辑) - matrix[1][1] = 0.5 - matrix[1][2] = 0.5 - matrix[1][3] = 2.0 - // ... 其余矩阵初始化代码(与原代码相同) + matrix[1][1] = 0.5 // 草→草 + matrix[1][2] = 0.5 // 草→水 + matrix[1][3] = 2.0 // 草→火 + matrix[1][4] = 2.0 // 草→飞行 + matrix[1][5] = 0.5 // 草→电 + matrix[1][7] = 0.5 // 草→地面 + matrix[1][9] = 2.0 // 草→冰 + matrix[1][12] = 0.0 // 草→光(免疫) + matrix[1][15] = 0.5 // 草→龙 + matrix[1][16] = 2.0 // 草→圣灵 + + matrix[2][1] = 2.0 // 水→草 + matrix[2][2] = 0.5 // 水→水 + matrix[2][3] = 0.5 // 水→火 + matrix[2][5] = 2.0 // 水→电 + matrix[2][6] = 0.5 // 水→机械 + matrix[2][9] = 0.5 // 水→冰 + matrix[2][15] = 0.5 // 水→龙 + matrix[2][16] = 2.0 // 水→圣灵 + + matrix[3][1] = 0.5 // 火→草 + matrix[3][2] = 2.0 // 火→水 + matrix[3][3] = 0.5 // 火→火 + matrix[3][6] = 0.5 // 火→机械 + matrix[3][7] = 2.0 // 火→地面 + matrix[3][9] = 0.5 // 火→冰 + matrix[3][15] = 0.5 // 火→龙 + matrix[3][16] = 2.0 // 火→圣灵 + + // 飞行系 + matrix[4][1] = 2 //飞行->草 + matrix[4][5] = 0.5 //飞行->电 + matrix[4][6] = 0.5 //飞行->机械 + matrix[4][11] = 2 //飞行->战斗 + matrix[4][17] = 0.5 //飞行->次元 + + //电系 + matrix[5][1] = 0.5 //电->草 + matrix[5][2] = 2 //电->水 + matrix[5][4] = 2 //电->飞行 + matrix[5][5] = 0.5 //电->电 + matrix[5][7] = 0 //电->地面 + matrix[5][13] = 2 //电->暗影 + matrix[5][14] = 0.5 //电->神秘 + matrix[5][16] = 0.5 //电->圣灵 + matrix[5][17] = 2 //电->次元 + + //机械 + matrix[6][2] = 0.5 //机械->水 + matrix[6][3] = 0.5 //机械->火 + matrix[6][5] = 0.5 //机械->电 + matrix[6][6] = 0.5 //机械->机械 + matrix[6][9] = 2 //机械->冰 + matrix[6][11] = 2 //机械->战斗 + matrix[6][17] = 0.5 //机械->次元 + + matrix[7][1] = 0.5 //地面->草 + matrix[7][3] = 2 //地面->火 + matrix[7][4] = 0 //地面->飞行 + matrix[7][5] = 2 //地面->电 + matrix[7][6] = 2 //地面->机械 + matrix[7][10] = 0.5 //地面->超能 + matrix[7][13] = 0.5 //地面->暗影 + matrix[7][15] = 0.5 //地面->龙 + matrix[7][16] = 0.5 //地面->圣灵 + + matrix[9][1] = 2 //->草 + matrix[9][2] = 0.5 //->水 + matrix[9][3] = 0.5 //->火 + matrix[9][4] = 2 //->飞行 + matrix[9][6] = 0.5 //->机械 + matrix[9][7] = 2 //->地面 + matrix[9][9] = 0.5 //->冰 + matrix[9][16] = 0.5 //->圣灵 + matrix[9][17] = 2 //->次元 + + matrix[10][6] = 0.5 //->机械 + matrix[10][10] = 0.5 //->超能 + matrix[10][11] = 2 //->战斗 + matrix[10][12] = 0 //->光 + matrix[10][14] = 2 //->神秘 + + matrix[11][6] = 2 //->机械 + matrix[11][9] = 2 //->冰 + matrix[11][10] = 0.5 //->超能 + matrix[11][11] = 0.5 //->战斗 + matrix[11][13] = 0.5 //->暗影 + matrix[11][15] = 2 //->龙 + matrix[11][16] = 2 //->圣灵 + + matrix[12][1] = 0 // + matrix[12][6] = 0.5 // + matrix[12][9] = 0.5 // + matrix[12][10] = 2 // + matrix[12][12] = 0.5 // + matrix[12][13] = 2 // + matrix[12][16] = 0.5 // + + matrix[13][6] = 0.5 // + matrix[13][9] = 0.5 // + matrix[13][10] = 2 // + matrix[13][12] = 0.5 // + matrix[13][13] = 2 // + matrix[13][16] = 0.5 // + matrix[13][17] = 2 // + + matrix[14][5] = 2 //->电 + matrix[14][7] = 0.5 //->地面 + matrix[14][11] = 0.5 //->战斗 + matrix[14][14] = 2 //->神秘 + matrix[14][16] = 2 //->圣灵 + + matrix[15][1] = 0.5 //->草 + matrix[15][2] = 0.5 //->水 + matrix[15][3] = 0.5 //->火 + matrix[15][5] = 0.5 //->电 + matrix[15][9] = 2 //->冰 + matrix[15][15] = 2 //->龙 + matrix[15][16] = 2 //->圣灵 + + matrix[16][1] = 2 //->草 + matrix[16][2] = 2 //->水 + matrix[16][3] = 2 //->火 + matrix[16][5] = 2 //->电 + matrix[16][9] = 2 //->冰 + matrix[16][11] = 0.5 //->战斗 + matrix[16][14] = 0.5 //->神秘 + matrix[16][15] = 0.5 //->龙 + + matrix[17][4] = 2 //->飞行 + matrix[17][6] = 2 //->机械 + matrix[17][9] = 0.5 //->冰 + matrix[17][10] = 2 //->超能 + matrix[17][13] = 0 //->暗影 return matrix } -// 获取元素组合(使用sync.Map.Load读取缓存) +// 获取元素组合 func (c *ElementCalculator) GetCombination(id int) (*ElementCombination, error) { - // 先查组合缓存 - if val, ok := c.combinationPool.Load(id); ok { - return val.(*ElementCombination), nil + c.mu.RLock() + if combo, exists := c.combinationPool[id]; exists { + c.mu.RUnlock() + return combo, nil } + c.mu.RUnlock() - // 再查错误缓存 - if val, ok := c.combinationErrCache.Load(id); ok { - return nil, val.(error) + c.mu.Lock() + defer c.mu.Unlock() + if combo, exists := c.combinationPool[id]; exists { + return combo, nil } - - // 双重检查:避免并发场景下重复创建(sync.Map无锁,但仍需防止重复计算) - // 先尝试再次读取(可能其他goroutine已创建) - if val, ok := c.combinationPool.Load(id); ok { - return val.(*ElementCombination), nil - } - if val, ok := c.combinationErrCache.Load(id); ok { - return nil, val.(error) - } - - // 创建新组合并缓存 combo, err := NewElementCombination(id) if err != nil { - c.combinationErrCache.Store(id, err) return nil, err } - c.combinationPool.Store(id, combo) + c.combinationPool[id] = combo return combo, nil } -// 计算攻击方X→防御方Y的系数(使用sync.Map缓存) +// 计算攻击方X→防御方Y的系数 func (c *ElementCalculator) GetOffensiveMultiplier(attackerXID, defenderYID int) (float64, error) { attackerX, err := c.GetCombination(attackerXID) if err != nil { @@ -262,31 +359,34 @@ func (c *ElementCalculator) GetOffensiveMultiplier(attackerXID, defenderYID int) } cacheKey := fmt.Sprintf("X%d→Y%d", attackerXID, defenderYID) - - // 尝试从缓存读取 - if val, ok := c.offensiveCache.Load(cacheKey); ok { - return val.(float64), nil + c.mu.RLock() + if val, exists := c.offensiveCache[cacheKey]; exists { + c.mu.RUnlock() + return val, nil } + c.mu.RUnlock() - // 缓存未命中,计算后存入缓存 result := c.calculateMultiplier(attackerX, defenderY) - c.offensiveCache.Store(cacheKey, result) + c.mu.Lock() + c.offensiveCache[cacheKey] = result + c.mu.Unlock() return result, nil } -// 核心计算逻辑(保持不变) +// 核心计算逻辑(严格遵循橙汁学姐规则) func (c *ElementCalculator) calculateMultiplier(attackerX, defenderY *ElementCombination) float64 { // 1. 单属性→单属性:直接查表 if !attackerX.IsDual() && !defenderY.IsDual() { return c.tableMatrix[attackerX.Primary][defenderY.Primary] } - // 2. 单属性→双属性:拆分防守方 + // 2. 单属性→双属性:拆分防守方,分类计算 if !attackerX.IsDual() { y1, y2 := defenderY.Primary, *defenderY.Secondary m1 := c.tableMatrix[attackerX.Primary][y1] m2 := c.tableMatrix[attackerX.Primary][y2] + // 单→双规则:双克制=4,含无效÷4,其他÷2 if m1 == 2 && m2 == 2 { return 4.0 } else if m1 == 0 || m2 == 0 { @@ -296,12 +396,21 @@ func (c *ElementCalculator) calculateMultiplier(attackerX, defenderY *ElementCom } } - // 3. 双属性→单属性:拆分攻击方 + // 3. 双属性→单属性:拆分攻击方,分类计算 if !defenderY.IsDual() { x1, x2 := attackerX.Primary, *attackerX.Secondary k1 := c.tableMatrix[x1][defenderY.Primary] k2 := c.tableMatrix[x2][defenderY.Primary] + // 补全默认值(未定义的普通关系为1.0) + if k1 == 0 && c.tableMatrix[x1][defenderY.Primary] != 0 { + k1 = 1.0 + } + if k2 == 0 && c.tableMatrix[x2][defenderY.Primary] != 0 { + k2 = 1.0 + } + + // 双→单规则:双克制=4,含无效÷4,其他÷2 if k1 == 2 && k2 == 2 { return 4.0 } else if k1 == 0 || k2 == 0 { @@ -311,21 +420,33 @@ func (c *ElementCalculator) calculateMultiplier(attackerX, defenderY *ElementCom } } - // 4. 双属性→双属性:拆分防守方为两个单属性,分别计算后取平均 + // 4. 双属性→双属性:拆分防守方为两个单属性,分别计算双→单后取平均 x1, x2 := attackerX.Primary, *attackerX.Secondary y1, y2 := defenderY.Primary, *defenderY.Secondary + // 计算攻击方对防守方第一个单属性(y1)的双→单系数 coeffY1 := c.calculateDualToSingle(x1, x2, y1) + // 计算攻击方对防守方第二个单属性(y2)的双→单系数 coeffY2 := c.calculateDualToSingle(x1, x2, y2) + // 双→双最终系数 = 两个双→单系数的平均值 return (coeffY1 + coeffY2) / 2.0 } -// 辅助函数:双属性攻击单属性的核心计算(保持不变) +// 辅助函数:双属性攻击单属性的核心计算(提取复用逻辑) func (c *ElementCalculator) calculateDualToSingle(attacker1, attacker2, defender ElementType) float64 { k1 := c.tableMatrix[attacker1][defender] k2 := c.tableMatrix[attacker2][defender] + // 补全默认值(未定义的普通关系为1.0) + if k1 == 0 && c.tableMatrix[attacker1][defender] != 0 { + k1 = 1.0 + } + if k2 == 0 && c.tableMatrix[attacker2][defender] != 0 { + k2 = 1.0 + } + + // 双→单规则应用 if k1 == 2 && k2 == 2 { return 4.0 } else if k1 == 0 || k2 == 0 { @@ -335,7 +456,7 @@ func (c *ElementCalculator) calculateDualToSingle(attacker1, attacker2, defender } } -// 测试用例(保持不变) +// 全场景测试用例 func TestAllScenarios(t *testing.T) { calculator := NewElementCalculator() @@ -348,10 +469,53 @@ func TestAllScenarios(t *testing.T) { // 测试2:单属性→双属性(火→冰龙) m2, _ := calculator.GetOffensiveMultiplier(3, 43) // 火→冰龙(9+15) + // 火→冰=2.0,火→龙=1.0 → 平均值=1.5 t.Logf("火→冰龙: %.2f(预期1.5)", m2) if math.Abs(m2-1.5) > 0.001 { t.Errorf("测试2错误: 实际%.2f", m2) } - // 其余测试用例与之前一致... + // 测试3:双属性→单属性(飞行超能→草) + m3, _ := calculator.GetOffensiveMultiplier(30, 1) // 飞行超能→草 + // 飞行→草=2.0,超能→草=1.0 → 平均值=1.5 + t.Logf("飞行超能→草: %.2f(预期1.5)", m3) + if math.Abs(m3-1.5) > 0.001 { + t.Errorf("测试3错误: 实际%.2f", m3) + } + + // 测试4:双属性→双属性(冰暗影→电战斗)预期=1.0 + m4, _ := calculator.GetOffensiveMultiplier(45, 35) + // 冰→电=1.0,冰→战斗=0.5,暗影→电=0.5,暗影→战斗=2.0 → 总和=4.0 → 平均值=1.0 + t.Logf("冰暗影→电战斗: %.4f(预期1.0)", m4) + if math.Abs(m4-1.0) > 0.001 { + t.Errorf("测试4错误: 实际%.4f", m4) + } + + // 测试5:双属性→双属性(电战斗→冰暗影)预期=1.375 + m5, _ := calculator.GetOffensiveMultiplier(35, 45) + // 电→冰=1.0,电→暗影=1.0,战斗→冰=2.0,战斗→暗影=1.5 → 总和=5.5 → 平均值=1.375 + t.Logf("电战斗→冰暗影: %.4f(预期1.375)", m5) + if math.Abs(m5-1.375) > 0.001 { + t.Errorf("测试5错误: 实际%.4f", m5) + } + + // 测试6:特殊免疫(飞行→地面) + m6, _ := calculator.GetOffensiveMultiplier(4, 7) + t.Logf("飞行→地面: %.2f(预期0.0)", m6) + if math.Abs(m6-0.0) > 0.001 { + t.Errorf("测试6错误: 实际%.2f", m6) + } + + // 测试7:光暗影→暗影(光→暗影=2.0,暗影→暗影=1.0 → 平均值=1.5) + m7, _ := calculator.GetOffensiveMultiplier(69, 13) + t.Logf("光暗影→暗影: %.2f(预期1.5)", m7) + if math.Abs(m7-1.5) > 0.001 { + t.Errorf("测试7错误: 实际%.2f", m7) + } + + // 测试8:缓存验证(复用测试4结果) + m8, _ := calculator.GetOffensiveMultiplier(46, 25) + if m8 != m4 { + t.Error("测试8错误: 缓存未命中") + } } diff --git a/logic/controller/pet.go b/logic/controller/pet.go index b41b0596..9dc49786 100644 --- a/logic/controller/pet.go +++ b/logic/controller/pet.go @@ -4,10 +4,11 @@ import ( "blazing/common/data/xmlres" "blazing/common/socket/errorcode" "blazing/common/utils" - "blazing/logic/service/pet" "blazing/logic/service/player" + "github.com/samber/lo" + "blazing/modules/blazing/model" "github.com/jinzhu/copier" @@ -187,7 +188,9 @@ func (h Controller) SetPetSkill(data *pet.ChangeSkillInfo, c *player.Player) (re } } - + onpet.SkillList = lo.UniqBy(onpet.SkillList, func(s model.SkillInfo) int { + return int(s.ID) + }) return &pet.ChangeSkillOutInfo{ CatchTime: data.CatchTime, }, 0 diff --git a/logic/go.mod b/logic/go.mod index 67cafc6c..18207b90 100644 --- a/logic/go.mod +++ b/logic/go.mod @@ -43,6 +43,7 @@ require ( github.com/redis/go-redis/v9 v9.5.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect + github.com/samber/lo v1.52.0 // indirect github.com/stretchr/testify v1.11.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect diff --git a/logic/go.sum b/logic/go.sum index 8a9d7583..a014813f 100644 --- a/logic/go.sum +++ b/logic/go.sum @@ -87,6 +87,8 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw= +github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=