277 lines
8.6 KiB
Go
277 lines
8.6 KiB
Go
package element
|
||
|
||
import (
|
||
"fmt"
|
||
"math"
|
||
)
|
||
|
||
// 元素类型枚举(覆盖所有单属性,ID与原配置完全对齐)
|
||
type ElementType int
|
||
|
||
const (
|
||
ElementTypeGrass ElementType = 1 // 草
|
||
ElementTypeWater ElementType = 2 // 水
|
||
ElementTypeFire ElementType = 3 // 火
|
||
ElementTypeFlying ElementType = 4 // 飞行
|
||
ElementTypeElectric ElementType = 5 // 电
|
||
ElementTypeSteel ElementType = 6 // 机械
|
||
ElementTypeGround ElementType = 7 // 地面
|
||
ElementTypeNormal ElementType = 8 // 普通
|
||
ElementTypeIce ElementType = 9 // 冰
|
||
ElementTypePsychic ElementType = 10 // 超能
|
||
ElementTypeFighting ElementType = 11 // 战斗
|
||
ElementTypeLight ElementType = 12 // 光
|
||
ElementTypeDark ElementType = 13 // 暗影
|
||
ElementTypeMythic ElementType = 14 // 神秘
|
||
ElementTypeDragon ElementType = 15 // 龙
|
||
ElementTypeSaint ElementType = 16 // 圣灵
|
||
ElementTypeDimension ElementType = 17 // 次元
|
||
ElementTypeAncient ElementType = 18 // 远古
|
||
ElementTypeDemon ElementType = 19 // 邪灵
|
||
ElementTypeNature ElementType = 20 // 自然
|
||
ElementTypeKing ElementType = 221 // 王
|
||
ElementTypeChaos ElementType = 222 // 混沌
|
||
ElementTypeDeity ElementType = 223 // 神灵
|
||
ElementTypeSamsara ElementType = 224 // 轮回
|
||
ElementTypeInsect ElementType = 225 // 虫
|
||
ElementTypeVoid ElementType = 226 // 虚空
|
||
)
|
||
|
||
// 全局常量定义(统一管理合法范围)
|
||
const (
|
||
maxMatrixSize = 227 // 矩阵维度(覆盖最大属性ID 226)
|
||
)
|
||
|
||
// 属性配置由 data/skillTypes.json 初始化,数据来自 seer-types-relation。
|
||
var validSingleElementIDs [maxMatrixSize]bool
|
||
|
||
// 元素名称映射(按ID直接索引,便于日志输出)
|
||
var elementNameMap [maxMatrixSize]string
|
||
|
||
// 双属性映射(key=双属性ID,value=组成的两个单属性ID)
|
||
var dualElementMap = map[int][2]int{}
|
||
|
||
// 元素组合结构体
|
||
type ElementCombination struct {
|
||
Primary ElementType // 主属性(按ID升序排序)
|
||
Secondary *ElementType // 副属性(双属性非空)
|
||
ID int // 组合唯一ID
|
||
}
|
||
|
||
// 全局预加载资源(程序启动时初始化,运行时只读)
|
||
var (
|
||
validCombinationIDs [maxMatrixSize]bool
|
||
elementCombinationPool [maxMatrixSize]ElementCombination
|
||
dualElementSecondaryPool [maxMatrixSize]ElementType
|
||
matrix [maxMatrixSize][maxMatrixSize]float64
|
||
Calculator *ElementCalculator
|
||
)
|
||
|
||
// init 预加载所有资源(程序启动时执行一次,无并发问题)
|
||
func init() {
|
||
initFullTableMatrix()
|
||
initElementCombinationPool()
|
||
Calculator = NewElementCalculator()
|
||
}
|
||
|
||
func initElementCombinationPool() {
|
||
for id, valid := range validSingleElementIDs {
|
||
if !valid {
|
||
continue
|
||
}
|
||
validCombinationIDs[id] = true
|
||
elementCombinationPool[id] = ElementCombination{
|
||
Primary: ElementType(id),
|
||
ID: id,
|
||
}
|
||
}
|
||
|
||
for dualID, atts := range dualElementMap {
|
||
primaryID, secondaryID := atts[0], atts[1]
|
||
primary, secondary := ElementType(primaryID), ElementType(secondaryID)
|
||
if primary > secondary {
|
||
primary, secondary = secondary, primary
|
||
}
|
||
|
||
dualElementSecondaryPool[dualID] = secondary
|
||
validCombinationIDs[dualID] = true
|
||
elementCombinationPool[dualID] = ElementCombination{
|
||
Primary: primary,
|
||
Secondary: &dualElementSecondaryPool[dualID],
|
||
ID: dualID,
|
||
}
|
||
}
|
||
}
|
||
|
||
func isValidCombinationID(id int) bool {
|
||
return id > 0 && id < maxMatrixSize && validCombinationIDs[id]
|
||
}
|
||
|
||
// IsDual 判断是否为双属性
|
||
func (ec *ElementCombination) IsDual() bool {
|
||
return ec.Secondary != nil
|
||
}
|
||
|
||
// Elements 获取所有属性列表
|
||
func (ec *ElementCombination) Elements() []ElementType {
|
||
if secondary := ec.Secondary; secondary != nil {
|
||
return []ElementType{ec.Primary, *secondary}
|
||
}
|
||
return []ElementType{ec.Primary}
|
||
}
|
||
|
||
// String 友好格式化输出
|
||
func (ec *ElementCombination) String() string {
|
||
if secondary := ec.Secondary; secondary != nil {
|
||
return fmt.Sprintf("(%s, %s)", elementNameMap[ec.Primary], elementNameMap[*secondary])
|
||
}
|
||
return fmt.Sprintf("(%s)", elementNameMap[ec.Primary])
|
||
}
|
||
|
||
// ElementCalculator 无锁元素克制计算器(所有倍数在初始化阶段预计算)
|
||
type ElementCalculator struct {
|
||
offensiveTable [maxMatrixSize][maxMatrixSize]float64
|
||
}
|
||
|
||
// NewElementCalculator 创建计算器实例(构建只读查表缓存)
|
||
func NewElementCalculator() *ElementCalculator {
|
||
c := &ElementCalculator{}
|
||
c.initOffensiveTable()
|
||
return c
|
||
}
|
||
|
||
func (c *ElementCalculator) initOffensiveTable() {
|
||
for attackerID, valid := range validCombinationIDs {
|
||
if !valid {
|
||
continue
|
||
}
|
||
attacker := &elementCombinationPool[attackerID]
|
||
for defenderID, valid := range validCombinationIDs {
|
||
if !valid {
|
||
continue
|
||
}
|
||
defender := &elementCombinationPool[defenderID]
|
||
c.offensiveTable[attackerID][defenderID] = c.calculateMultiplier(attacker, defender)
|
||
}
|
||
}
|
||
}
|
||
|
||
// getMatrixValue 直接返回矩阵值(修复核心问题:不再将0转换为1)
|
||
func (c *ElementCalculator) getMatrixValue(attacker, defender ElementType) float64 {
|
||
return matrix[attacker][defender]
|
||
}
|
||
|
||
// GetCombination 获取元素组合(直接按ID索引)
|
||
func (c *ElementCalculator) GetCombination(id int) (*ElementCombination, error) {
|
||
if !isValidCombinationID(id) {
|
||
return nil, fmt.Errorf("invalid element combination ID: %d", id)
|
||
}
|
||
return &elementCombinationPool[id], nil
|
||
}
|
||
|
||
// GetOffensiveMultiplier 计算攻击方→防御方的克制倍数(只读查表)
|
||
func (c *ElementCalculator) GetOffensiveMultiplier(attackerID, defenderID int) (float64, error) {
|
||
if !isValidCombinationID(attackerID) {
|
||
return 0, fmt.Errorf("attacker invalid: invalid element combination ID: %d", attackerID)
|
||
}
|
||
if !isValidCombinationID(defenderID) {
|
||
return 0, fmt.Errorf("defender invalid: invalid element combination ID: %d", defenderID)
|
||
}
|
||
return c.offensiveTable[attackerID][defenderID], nil
|
||
}
|
||
|
||
// calculateMultiplier 核心克制计算逻辑
|
||
func (c *ElementCalculator) calculateMultiplier(attacker, defender *ElementCombination) float64 {
|
||
if !attacker.IsDual() && !defender.IsDual() {
|
||
return c.getMatrixValue(attacker.Primary, defender.Primary)
|
||
}
|
||
|
||
if !attacker.IsDual() {
|
||
y1, y2 := defender.Primary, *defender.Secondary
|
||
m1 := c.getMatrixValue(attacker.Primary, y1)
|
||
m2 := c.getMatrixValue(attacker.Primary, y2)
|
||
switch {
|
||
case m1 == 2 && m2 == 2:
|
||
return 4.0
|
||
case m1 == 0 || m2 == 0:
|
||
return (m1 + m2) / 4.0
|
||
default:
|
||
return (m1 + m2) / 2.0
|
||
}
|
||
}
|
||
|
||
if !defender.IsDual() {
|
||
return c.calculateDualToSingle(attacker.Primary, *attacker.Secondary, defender.Primary)
|
||
}
|
||
|
||
x1, x2 := attacker.Primary, *attacker.Secondary
|
||
y1, y2 := defender.Primary, *defender.Secondary
|
||
coeffY1 := c.calculateDualToSingle(x1, x2, y1)
|
||
coeffY2 := c.calculateDualToSingle(x1, x2, y2)
|
||
return (coeffY1 + coeffY2) / 2.0
|
||
}
|
||
|
||
// calculateDualToSingle 辅助函数:双→单计算
|
||
func (c *ElementCalculator) calculateDualToSingle(attacker1, attacker2, defender ElementType) float64 {
|
||
k1 := c.getMatrixValue(attacker1, defender)
|
||
k2 := c.getMatrixValue(attacker2, defender)
|
||
switch {
|
||
case k1 == 2 && k2 == 2:
|
||
return 4.0
|
||
case k1 == 0 || k2 == 0:
|
||
return (k1 + k2) / 4.0
|
||
default:
|
||
return (k1 + k2) / 2.0
|
||
}
|
||
}
|
||
|
||
// TestAllScenarios 全场景测试(验证预加载和计算逻辑)
|
||
func TestAllScenarios() {
|
||
m1, _ := Calculator.GetOffensiveMultiplier(1, 2)
|
||
fmt.Println("草→水: %.2f(预期2.0)", m1)
|
||
if math.Abs(m1-2.0) > 0.001 {
|
||
fmt.Println("测试1失败:实际%.2f", m1)
|
||
}
|
||
|
||
m2, _ := Calculator.GetOffensiveMultiplier(222, 226)
|
||
fmt.Println("混沌→虚空: %.2f(预期0.0)", m2)
|
||
if math.Abs(m2-0.0) > 0.001 {
|
||
fmt.Println("测试2失败:实际%.2f", m2)
|
||
}
|
||
|
||
m3, _ := Calculator.GetOffensiveMultiplier(3, 43)
|
||
fmt.Println("火→冰龙: %.2f(预期1.5)", m3)
|
||
if math.Abs(m3-1.5) > 0.001 {
|
||
fmt.Println("测试3失败:实际%.2f", m3)
|
||
}
|
||
|
||
m4, _ := Calculator.GetOffensiveMultiplier(92, 223)
|
||
fmt.Println("混沌暗影→神灵: %.2f(预期1.25)", m4)
|
||
if math.Abs(m4-1.25) > 0.001 {
|
||
fmt.Println("测试4失败:实际%.2f", m4)
|
||
}
|
||
|
||
m5, _ := Calculator.GetOffensiveMultiplier(113, 98)
|
||
fmt.Println("虚空邪灵→混沌远古: %.2f(预期0.875", m5)
|
||
if math.Abs(m5-0.875) > 0.001 {
|
||
fmt.Println("测试5失败:实际%.2f", m5)
|
||
}
|
||
|
||
m6, _ := Calculator.GetOffensiveMultiplier(113, 98)
|
||
if math.Abs(m6-m5) > 0.001 {
|
||
fmt.Println("测试6失败:缓存未命中")
|
||
}
|
||
|
||
m7, _ := Calculator.GetOffensiveMultiplier(5, 7)
|
||
fmt.Println("电→地面: %.2f(预期0.0)", m7)
|
||
if math.Abs(m7-0.0) > 0.001 {
|
||
fmt.Println("测试7失败:实际%.2f", m7)
|
||
}
|
||
|
||
m8, _ := Calculator.GetOffensiveMultiplier(35, 7)
|
||
fmt.Println("电战斗→地面: %.2f(预期0.25)", m8)
|
||
if math.Abs(m8-0.25) > 0.001 {
|
||
fmt.Println("测试8失败:实际%.2f", m8)
|
||
}
|
||
}
|