diff --git a/logic/service/player/Monster.go b/logic/service/player/Monster.go index d7ebafaf..ca1c0694 100644 --- a/logic/service/player/Monster.go +++ b/logic/service/player/Monster.go @@ -6,7 +6,6 @@ import ( "blazing/modules/blazing/model" "strings" "sync/atomic" - "time" "github.com/gogf/gf/v2/util/gconv" "github.com/gogf/gf/v2/util/grand" @@ -71,7 +70,7 @@ func (p *Player) genMonster() { Knockout: false, // 颜色矩阵:标准 RGBA 矩阵(20个uint8,符合 [20]uint8 数组类型) // 矩阵含义:R=100%、G=100%、B=100%、A=100%,无颜色偏移 - ColorMatrixFilter: RandomControlledMatrix_IdentityAlpha(time.Now().UnixNano() + int64(ttt.Id)), + ColorMatrixFilter: RandomMatrixDefaultAlphaNoSingleColor(int64(grand.Intn(5000)), 0.8), } // g.Dump(ttt.ShinyInfo) // ttt.Shiny = 0 //待确认是否刷新异色 diff --git a/logic/service/player/color.go b/logic/service/player/color.go index 98f3c5dd..86c1594c 100644 --- a/logic/service/player/color.go +++ b/logic/service/player/color.go @@ -4,62 +4,148 @@ import ( grand "math/rand" ) -// 生成 [20]uint8,最后 5 个字节固定为 [0,0,0,1,0] -// 适用于“byte 直接作为系数(1 表示 1.0)”的场景。 -// 其它值在接近单位矩阵的范围内随机,尽量避免白化。 -func RandomControlledMatrix_IdentityAlpha(seed int64) [20]uint8 { +// RandomMatrixNoSingleColor 生成不会导致“只有一种颜色”的 4x5 uint8 矩阵(共20字节)。 +// 约束与策略: +// - 仍然适用“直接把 uint8 原样当系数使用”的前提,保证不会白化或溢出: +// - 当某行有 multiplier != 0 时,offset 强制为 0;且 multipliers 最大为 1 且每行最多一个 1(L1<=1) +// - 当某行使用常量偏移模式(ModeB)时,四个 multiplier 全为 0,offset 为常量(在安全中间区) +// +// - 为避免“只有一种颜色”的情况,我们强制: +// - 至少有一个行使用 ModeA(依赖输入通道)。若有多个 ModeA,则这些 ModeA 的“pick”尽量互不相同(从 0..3 中抽样不重复), +// 这样 destR/destG/destB 至少有不同的输入来源,从而随输入像素变化产生多色彩输出。 +// - 如果有多个 ModeB(常量行),它们的 offset 不相同,且不同时全为相同值,避免所有通道都变为同一常量。 +// +// - alphaRow (最后5字节) 由调用方指定并固定不变(默认 [0,0,0,1,0])。 +// +// brightChance: 0..1,用于决定每行是否优先使用常量偏移 (ModeB);函数内部会修正以保证多样性。 +func RandomMatrixNoSingleColor(seed int64, alphaRow [5]uint8, brightChance float64) [20]uint8 { var out [20]uint8 + + // clamp brightChance + if brightChance < 0 { + brightChance = 0 + } + if brightChance > 1 { + brightChance = 1 + } + + // offset 范围(中间区,避免接近 0 或 255) + const minOffset = 40 + const maxOffset = 215 + grand.Seed(seed) - // 更保守范围(在直接 uint8 当系数时,1 表示 1.0) - diagMin, diagMax := 0.95, 1.05 // 对角线乘数(接近 1) - crossMin, crossMax := -1.0, 1.0 // 跨通道系数(小整数,可能为负,下面做 signed->uint8 映射) - offsetMin, offsetMax := -8.0, 8.0 // 偏移(以整数为单位) - - idx := 0 - for row := 0; row < 3; row++ { // 只生成前三行,最后一行固定 - for col := 0; col < 5; col++ { - var b uint8 - if col < 4 { - // multiplier (float around 1.0) -> round to nearest int (since direct uint8 coeff) - var v float64 - if col == row { - v = diagMin + grand.Float64()*(diagMax-diagMin) - } else { - v = crossMin + grand.Float64()*(crossMax-crossMin) - } - // 四舍五入到最近整数并允许负数:用 int8 再转为 uint8(保持二进制两补表示) - iv := int(v + 0.5) - // clamp to int8 range - if iv < -128 { - iv = -128 - } - if iv > 127 { - iv = 127 - } - b = uint8(int8(iv)) - } else { - // offset 也用 int8 存(-128..127),转为 uint8 的二进制表示 - off := offsetMin + grand.Float64()*(offsetMax-offsetMin) - ioff := int(off + 0.5) - if ioff < -128 { - ioff = -128 - } - if ioff > 127 { - ioff = 127 - } - b = uint8(int8(ioff)) - } - out[idx] = b - idx++ + // 初步决定每行的模式(true=ModeB(常量偏移),false=ModeA(拷贝/置换)) + modeB := make([]bool, 3) + modeBCount := 0 + for r := 0; r < 3; r++ { + if grand.Float64() < brightChance { + modeB[r] = true + modeBCount++ + } else { + modeB[r] = false } } - // 明确设置 alpha 行为 [0,0,0,1,0](直接 uint8 值) - out[15] = uint8(0) // a00 - out[16] = uint8(0) // a01 - out[17] = uint8(0) // a02 - out[18] = uint8(1) // a03 (alpha multiplier = 1) - out[19] = uint8(0) // a04 (alpha offset = 0) + // 强制至少一个 ModeA(依赖输入),以避免全常量图像 + if modeBCount == 3 { + // 随机把一行改为 ModeA + which := int(grand.Intn(3)) + modeB[which] = false + modeBCount-- + } + + // 为 ModeA 行分配不重复的 pick(0..3),尽量保证不同输入来源 + // 先收集需要 pick 的行索引 + pickRows := make([]int, 0, 3) + for r := 0; r < 3; r++ { + if !modeB[r] { + pickRows = append(pickRows, r) + } + } + // available inputs 0..3 + available := []int{0, 1, 2, 3} + // 随机打乱 available + for i := len(available) - 1; i > 0; i-- { + j := int(grand.Intn(i + 1)) + available[i], available[j] = available[j], available[i] + } + // 给每个需要 pick 的行分配不同输入(如果 pickRows 数量 <= 4 则肯定可不重复) + picks := make(map[int]int) // row -> pick + for i, row := range pickRows { + picks[row] = available[i%len(available)] + } + + // 为 ModeB 行分配 offsets,保证多个 ModeB 行的 offset 不相同且不等于边界值 + usedOffsets := map[int]bool{} + for r := 0; r < 3; r++ { + if modeB[r] { + // 随机取一个不同的 offset + var off int + for tries := 0; tries < 10; tries++ { + off = minOffset + int(grand.Intn(maxOffset-minOffset+1)) + if !usedOffsets[off] { + break + } + } + // 若多次冲突仍然存在,仍使用最后一个 off(collision 可能性低) + usedOffsets[off] = true + // 写入 multipliers 全 0,offset = off + for c := 0; c < 4; c++ { + out[r*5+c] = 0 + } + out[r*5+4] = uint8(off) + } else { + // ModeA:拷贝/置换,只有一个 multiplier=1(pick),其他为0,offset=0 + pick := picks[r] + for c := 0; c < 4; c++ { + if c == pick { + out[r*5+c] = 1 + } else { + out[r*5+c] = 0 + } + } + out[r*5+4] = 0 + } + } + + // 额外保护:防止三行输出仍然完全相同的常量或同一输入通道(极小概率) + // 情况1:如果三行都是 ModeA 且 picks 全相同 -> 强制将其中一行改为拷贝另一个不同的输入(或改为 ModeB) + if !modeB[0] && !modeB[1] && !modeB[2] { + if picks[0] == picks[1] && picks[1] == picks[2] { + // 将第三行改为 pick = (picks[2]+1)%4 保证不同 + newPick := (picks[2] + 1) % 4 + for c := 0; c < 4; c++ { + if c == newPick { + out[2*5+c] = 1 + } else { + out[2*5+c] = 0 + } + } + out[2*5+4] = 0 + } + } + + // 情况2:如果三行都是 ModeB 且 offsets 有重复(理论上不可能因为我们尽量保证唯一),强制调整 + if modeB[0] && modeB[1] && modeB[2] { + if out[4] == out[9] && out[9] == out[14] { + // adjust third offset by +17 (并截断到范围) + newOff := int(out[14]) + 17 + if newOff > maxOffset { + newOff = minOffset + ((newOff - minOffset) % (maxOffset - minOffset + 1)) + } + out[14] = uint8(newOff) + } + } + + // 最后 5 个字节由 caller 指定(alpha 行)并固定 + for i := 0; i < 5; i++ { + out[15+i] = alphaRow[i] + } + return out } + +func RandomMatrixDefaultAlphaNoSingleColor(seed int64, brightChance float64) [20]uint8 { + return RandomMatrixNoSingleColor(seed, [5]uint8{0, 0, 0, 1, 0}, brightChance) +}