diff --git a/logic/service/fight/damage.go b/logic/service/fight/battle/damage.go similarity index 100% rename from logic/service/fight/damage.go rename to logic/service/fight/battle/damage.go diff --git a/logic/service/fight/battle/effect/EnumEffectTrigger.go b/logic/service/fight/battle/effect/EnumEffectTrigger.go new file mode 100644 index 000000000..b03fb0553 --- /dev/null +++ b/logic/service/fight/battle/effect/EnumEffectTrigger.go @@ -0,0 +1,54 @@ +package effect + +import "github.com/tnnmigga/enum" + +type EnumEffectTrigger string + +var EffectTrigger = enum.New[struct { + OnBattleStart EnumEffectTrigger `enum:"OnBattleStart"` // 战斗开始时触发 + BeforeSort EnumEffectTrigger `enum:"BeforeSort"` // 先手顺序判定前触发 + BeforeUseSkillCheck EnumEffectTrigger `enum:"BeforeUseSkillCheck"` // 使用技能前检查(PP、状态等) + AfterUseSkillCheck EnumEffectTrigger `enum:"AfterUseSkillCheck"` // 使用技能检查后触发 + BeforeMultiHit EnumEffectTrigger `enum:"BeforeMultiHit"` // 多段攻击开始前触发 + BeforeHit EnumEffectTrigger `enum:"BeforeHit"` // 攻击命中前触发 + OnCritPreDamage EnumEffectTrigger `enum:"OnCritPreDamage"` // 暴击判定成功且伤害计算前触发 + PreDamage EnumEffectTrigger `enum:"PreDamage"` // 技能伤害计算前触发(增伤/减伤等) + OnHit EnumEffectTrigger `enum:"OnHit"` // 技能命中时触发 + OnMiss EnumEffectTrigger `enum:"OnMiss"` // 技能未命中时触发 + AfterAttacked EnumEffectTrigger `enum:"AfterAttacked"` // 被攻击后触发(受击判定) + OnDefeat EnumEffectTrigger `enum:"OnDefeat"` // 精灵被击败时触发 + SkillUseEnd EnumEffectTrigger `enum:"SkillUseEnd"` // 技能使用结束后触发 + OnBeforeCalculateDamage EnumEffectTrigger `enum:"OnBeforeCalculateDamage"` // 最终伤害计算前触发 + OnDamage EnumEffectTrigger `enum:"OnDamage"` // 造成伤害时触发 + Shield EnumEffectTrigger `enum:"Shield"` // 护盾值变化时触发 + PostDamage EnumEffectTrigger `enum:"PostDamage"` // 伤害结算后触发(血量扣除后) + OnCritPostDamage EnumEffectTrigger `enum:"OnCritPostDamage"` // 暴击伤害结算后触发 + OnTransform EnumEffectTrigger `enum:"OnTransform"` // 精灵变形/进化时触发 + OnTransformEnd EnumEffectTrigger `enum:"OnTransformEnd"` // 变形/进化结束时触发 + BeforeTransform EnumEffectTrigger `enum:"BeforeTransform"` // 变形/进化前触发 + AfterTransform EnumEffectTrigger `enum:"AfterTransform"` // 变形/进化后触发 + TurnStart EnumEffectTrigger `enum:"TurnStart"` // 回合开始时触发 + TurnEnd EnumEffectTrigger `enum:"TurnEnd"` // 回合结束时触发 + OnBeforeAddMark EnumEffectTrigger `enum:"OnBeforeAddMark"` // 添加印记前触发 + OnAnyMarkAdded EnumEffectTrigger `enum:"OnAnyMarkAdded"` // 任何印记添加时触发 + OnRemoveMark EnumEffectTrigger `enum:"OnRemoveMark"` // 移除印记时触发 + OnMarkCreated EnumEffectTrigger `enum:"OnMarkCreated"` // 印记创建时触发 + OnMarkDestroy EnumEffectTrigger `enum:"OnMarkDestroy"` // 印记销毁时触发 + OnMarkDurationEnd EnumEffectTrigger `enum:"OnMarkDurationEnd"` // 印记持续回合结束时触发 + OnStackBefore EnumEffectTrigger `enum:"OnStackBefore"` // 堆叠效果前触发 + OnStack EnumEffectTrigger `enum:"OnStack"` // 堆叠效果触发 + OnBeforeConsumeStack EnumEffectTrigger `enum:"OnBeforeConsumeStack"` // 消耗堆叠前触发 + OnConsumeStack EnumEffectTrigger `enum:"OnConsumeStack"` // 消耗堆叠时触发 + OnBeforeHeal EnumEffectTrigger `enum:"OnBeforeHeal"` // 治疗前触发 + OnHeal EnumEffectTrigger `enum:"OnHeal"` // 治疗生效时触发 + BeforeRageGain EnumEffectTrigger `enum:"BeforeRageGain"` // 增怒前触发 + BeforeRageLoss EnumEffectTrigger `enum:"BeforeRageLoss"` // 减怒前触发 + OnRageGain EnumEffectTrigger `enum:"OnRageGain"` // 增怒时触发 + OnRageLoss EnumEffectTrigger `enum:"OnRageLoss"` // 减怒时触发 + OnSwitchIn EnumEffectTrigger `enum:"OnSwitchIn"` // 精灵出战/上场时触发 + OnSwitchOut EnumEffectTrigger `enum:"OnSwitchOut"` // 精灵下场时触发 + OnOwnerSwitchIn EnumEffectTrigger `enum:"OnOwnerSwitchIn"` // 所属玩家精灵出战时触发 + OnOwnerSwitchOut EnumEffectTrigger `enum:"OnOwnerSwitchOut"` // 所属玩家精灵下场时触发 + BeforeEffect EnumEffectTrigger `enum:"BeforeEffect"` // 效果生效前触发 + AfterEffect EnumEffectTrigger `enum:"AfterEffect"` // 效果生效后触发 +}]() diff --git a/logic/service/fight/battle/effect/effect.go b/logic/service/fight/battle/effect/effect.go new file mode 100644 index 000000000..6bf724769 --- /dev/null +++ b/logic/service/fight/battle/effect/effect.go @@ -0,0 +1,215 @@ +package effect + +import ( + "fmt" + "sort" + + "github.com/badu/bus" + "github.com/tnnmigga/enum" +) + +// ======================== +// BattleMode 枚举 +// ======================== + +type EnumBattleMode string + +var BattleMode = enum.New[struct { + PVE EnumBattleMode `enum:"3"` + PVP EnumBattleMode `enum:"1"` +}]() + +// ======================== +// Effect 框架 +// ======================== + +type ContextType interface { + SourceID() string +} + +type EffectContext struct { + Parent ContextType + Trigger EnumEffectTrigger + Container *EffectContainer + Effect *Effect + Available bool + Success bool + Done bool +} + +func (c *EffectContext) Stop() { c.Done = true } +func (c *EffectContext) SourceID() string { + if c.Container != nil { + return c.Container.ID + } + return "unknown" +} + +type EffectFunc func(ctx *EffectContext, next func()) + +type Effect struct { + ID string + Priority int + Triggers []EnumEffectTrigger + Condition func(*EffectContext) bool + Apply EffectFunc +} + +type EffectContainer struct { + ID string + Effects []*Effect + Subs []*bus.Listener[*EffectContext] + Battle *Battle + Parent ContextType +} + +func NewEffectContainer(id string, effects []*Effect, battle *Battle, parent ContextType) *EffectContainer { + c := &EffectContainer{ + ID: id, + Effects: effects, + Battle: battle, + Parent: parent, + } + // 自动订阅 triggers + for _, e := range effects { + for _, trig := range e.Triggers { + sub := battle.GetTopic(trig).Sub(func(ctx *EffectContext) { + if ctx.Available && e.Apply != nil { + ctx.Effect = e + c.executeWithPriority([]*EffectContext{ctx}) + } + }) + c.Subs = append(c.Subs, sub) + } + } + return c +} + +// done/next 执行队列,按优先级 +func (c *EffectContainer) executeWithPriority(queue []*EffectContext) { + sort.SliceStable(queue, func(i, j int) bool { + return queue[i].Effect.Priority > queue[j].Effect.Priority + }) + var runNext func(idx int) + runNext = func(idx int) { + if idx >= len(queue) { + return + } + ctx := queue[idx] + if ctx.Available { + ctx.Effect.Apply(ctx, func() { + runNext(idx + 1) + }) + } else { + runNext(idx + 1) + } + } + runNext(0) +} + +// ======================== +// Battle +// ======================== + +type Battle struct { + Turn int + topics map[EnumEffectTrigger]*bus.Topic[*EffectContext] +} + +func NewBattle() *Battle { + allTriggers := []EnumEffectTrigger{ + EffectTrigger.OnBattleStart, + EffectTrigger.BeforeSort, + EffectTrigger.BeforeUseSkillCheck, + EffectTrigger.AfterUseSkillCheck, + EffectTrigger.BeforeMultiHit, + EffectTrigger.BeforeHit, + EffectTrigger.OnCritPreDamage, + EffectTrigger.PreDamage, + EffectTrigger.OnHit, + EffectTrigger.OnMiss, + EffectTrigger.AfterAttacked, + EffectTrigger.OnDefeat, + EffectTrigger.SkillUseEnd, + EffectTrigger.OnBeforeCalculateDamage, + EffectTrigger.OnDamage, + EffectTrigger.Shield, + EffectTrigger.PostDamage, + EffectTrigger.OnCritPostDamage, + EffectTrigger.OnTransform, + EffectTrigger.OnTransformEnd, + EffectTrigger.BeforeTransform, + EffectTrigger.AfterTransform, + EffectTrigger.TurnStart, + EffectTrigger.TurnEnd, + EffectTrigger.OnBeforeAddMark, + EffectTrigger.OnAnyMarkAdded, + EffectTrigger.OnRemoveMark, + EffectTrigger.OnMarkCreated, + EffectTrigger.OnMarkDestroy, + EffectTrigger.OnMarkDurationEnd, + EffectTrigger.OnStackBefore, + EffectTrigger.OnStack, + EffectTrigger.OnBeforeConsumeStack, + EffectTrigger.OnConsumeStack, + EffectTrigger.OnBeforeHeal, + EffectTrigger.OnHeal, + EffectTrigger.BeforeRageGain, + EffectTrigger.BeforeRageLoss, + EffectTrigger.OnRageGain, + EffectTrigger.OnRageLoss, + EffectTrigger.OnSwitchIn, + EffectTrigger.OnSwitchOut, + EffectTrigger.OnOwnerSwitchIn, + EffectTrigger.OnOwnerSwitchOut, + EffectTrigger.BeforeEffect, + EffectTrigger.AfterEffect, + } + topics := make(map[EnumEffectTrigger]*bus.Topic[*EffectContext]) + for _, trig := range allTriggers { + topics[trig] = bus.NewTopic[*EffectContext]() + } + return &Battle{Turn: 0, topics: topics} +} + +func (b *Battle) GetTopic(trig EnumEffectTrigger) *bus.Topic[*EffectContext] { + return b.topics[trig] +} + +func (b *Battle) NextTurn(containers []*EffectContainer) { + b.Turn++ + fmt.Printf("=== 回合 %d 开始 ===\n", b.Turn) + b.PublishTrigger(EffectTrigger.TurnStart, containers) + fmt.Println("=== 玩家操作阶段 ===") + b.PublishTrigger(EffectTrigger.TurnEnd, containers) + fmt.Printf("=== 回合 %d 结束 ===\n\n", b.Turn) +} + +func (b *Battle) PublishTrigger(trigger EnumEffectTrigger, containers []*EffectContainer) { + for _, c := range containers { + for _, e := range c.Effects { + for _, t := range e.Triggers { + if t == trigger { + ctx := &EffectContext{ + Parent: c.Parent, + Trigger: trigger, + Container: c, + Effect: e, + Available: true, + } + b.GetTopic(trigger).Pub(ctx) + } + } + } + } +} + +// ======================== +// 示例 +// ======================== + +type Player struct { + ID string +} + +func (p *Player) SourceID() string { return p.ID } diff --git a/logic/service/fight/battle/effect/effect_test.go b/logic/service/fight/battle/effect/effect_test.go new file mode 100644 index 000000000..1002acbcd --- /dev/null +++ b/logic/service/fight/battle/effect/effect_test.go @@ -0,0 +1,40 @@ +package effect + +import ( + "fmt" + "testing" + "time" +) + +func Test_mainTT(t *testing.T) { + battle := NewBattle() + player := &Player{ID: "player1"} + + container := NewEffectContainer("container1", []*Effect{ + { + ID: "startEffect", + Priority: 10, + Triggers: []EnumEffectTrigger{EffectTrigger.TurnStart}, + Apply: func(ctx *EffectContext, next func()) { + fmt.Println("回合开始效果: 增加护盾") + next() + }, + }, + { + ID: "endEffect", + Priority: 5, + Triggers: []EnumEffectTrigger{EffectTrigger.TurnEnd}, + Apply: func(ctx *EffectContext, next func()) { + fmt.Println("回合结束效果: 恢复怒气") + next() + }, + }, + }, battle, player) + + for i := 0; i < 2; i++ { + battle.NextTurn([]*EffectContainer{container}) + time.Sleep(500 * time.Millisecond) + } + + fmt.Println("测试 BattleMode 枚举:", BattleMode.PVE, BattleMode.PVP) +} diff --git a/logic/service/fight/fight.md b/logic/service/fight/battle/fight.md similarity index 100% rename from logic/service/fight/fight.md rename to logic/service/fight/battle/fight.md diff --git a/logic/service/fight/fight_test.go b/logic/service/fight/battle/fight_test.go similarity index 100% rename from logic/service/fight/fight_test.go rename to logic/service/fight/battle/fight_test.go diff --git a/logic/service/fight/mode.go b/logic/service/fight/battle/mode.go similarity index 63% rename from logic/service/fight/mode.go rename to logic/service/fight/battle/mode.go index bb95cc0b5..719a7f8a0 100644 --- a/logic/service/fight/mode.go +++ b/logic/service/fight/battle/mode.go @@ -6,6 +6,6 @@ import "github.com/tnnmigga/enum" type EnumBattleMode int var BattleMode = enum.New[struct { - PVE EnumBattleMode // 玩家 vs AI - PVP EnumBattleMode // 玩家 vs 玩家 + PVE EnumBattleMode `enum:"3"` + PVP EnumBattleMode `enum:"1"` }]() diff --git a/logic/service/fight/fight.go b/logic/service/fight/fightcmd.go similarity index 100% rename from logic/service/fight/fight.go rename to logic/service/fight/fightcmd.go diff --git a/logic/service/fight/md/elo-rating-system.md b/logic/service/fight/md/elo-rating-system.md new file mode 100644 index 000000000..465e7474b --- /dev/null +++ b/logic/service/fight/md/elo-rating-system.md @@ -0,0 +1,244 @@ +# ELO评级系统 + +## 概述 + +为匹配框架引入了ELO评级系统,为每个规则集单独维护ELO评级,提供更精确的玩家技能评估和匹配质量。 + +## 核心特性 + +### 🎯 规则集分离 +- 每个规则集(如休闲模式、竞技模式)独立维护ELO评级 +- 玩家在不同规则集下有不同的ELO分数 +- 支持无限扩展新的规则集 + +### 📊 标准ELO算法 +- 基于经典ELO评级系统 +- 期望得分计算:`1 / (1 + 10^((对手ELO - 自己ELO) / 400))` +- 新ELO = 旧ELO + K * (实际得分 - 期望得分) +- 支持胜利(1分)、失败(0分)、平局(0.5分) + +### ⚙️ 动态K因子 +- **新手** (< 30场): K = 32 (快速调整) +- **普通** (30-100场): K = 24 (中等调整) +- **老手** (> 100场): K = 16 (稳定调整) + +### 🛡️ 安全边界 +- ELO范围:100 - 3000 +- 初始ELO:1200 +- 自动记录历史最高ELO + +## 数据库设计 + +### player_elo_ratings 表 + +```sql +CREATE TABLE player_elo_ratings ( + player_id TEXT NOT NULL, -- 玩家ID + rule_set_id TEXT NOT NULL, -- 规则集ID + elo_rating INTEGER DEFAULT 1200, -- 当前ELO评级 + games_played INTEGER DEFAULT 0, -- 游戏场次 + wins INTEGER DEFAULT 0, -- 胜利次数 + losses INTEGER DEFAULT 0, -- 失败次数 + draws INTEGER DEFAULT 0, -- 平局次数 + highest_elo INTEGER DEFAULT 1200, -- 历史最高ELO + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + + PRIMARY KEY (player_id, rule_set_id) +); +``` + +### 核心函数 + +- `get_or_create_player_elo()` - 获取或创建ELO记录 +- `update_player_elo()` - 更新单个玩家ELO +- `batch_update_player_elos()` - 批量更新ELO(战斗结束) +- `get_elo_leaderboard()` - 获取排行榜 + +## API端点 + +### 排行榜 +``` +GET /api/v1/elo/leaderboard/:ruleSetId?limit=50&offset=0 +``` + +### 玩家ELO信息 +``` +GET /api/v1/elo/player/:playerId/:ruleSetId +GET /api/v1/elo/player/:playerId # 所有规则集 +``` + +### 统计信息 +``` +GET /api/v1/elo/statistics/:ruleSetId +``` + +### 战斗预测 +``` +GET /api/v1/elo/predict/:playerAId/:playerBId/:ruleSetId +``` + +### 配置信息 +``` +GET /api/v1/elo/config +GET /api/v1/elo/win-rate/:eloDifference +``` + +## 系统集成 + +### 战斗结束自动更新 + +战斗结束时,`battleReportService` 会自动: + +1. 获取两个玩家的当前ELO +2. 根据战斗结果计算新ELO +3. 原子性批量更新数据库 +4. 记录详细日志 + +```typescript +// 在 battleReportService.ts 中 +if (battleData.ruleSetId && battleResult !== 'abandoned') { + await eloService.processBattleEloUpdate( + battleData.playerAId, + battleData.playerBId, + winnerId, + battleData.ruleSetId + ) +} +``` + +### 匹配系统集成 + +匹配系统已支持规则集分离,ELO系统可以: + +- 基于ELO进行更精确的匹配 +- 预测战斗结果概率 +- 提供匹配质量评估 + +## 部署步骤 + +### 1. 数据库迁移 + +在Supabase SQL编辑器中执行: + +```sql +-- 执行ELO系统迁移 +\i packages/database/sql/06_add_elo_system.sql +``` + +### 2. 服务器更新 + +ELO系统已集成到现有服务器中,无需额外配置。 + +### 3. 测试验证 + +```bash +# 测试ELO计算逻辑 +cd packages/server && npx tsx src/test-elo.ts + +# 启动服务器测试API +node dist/cli.js server --port 8102 +``` + +## 使用示例 + +### 获取排行榜 + +```bash +curl "http://localhost:8102/api/v1/elo/leaderboard/casual_standard_ruleset?limit=10" +``` + +### 查看玩家ELO + +```bash +curl "http://localhost:8102/api/v1/elo/player/player123/casual_standard_ruleset" +``` + +### 预测战斗结果 + +```bash +curl "http://localhost:8102/api/v1/elo/predict/player1/player2/competitive_ruleset" +``` + +## 配置选项 + +ELO系统支持运行时配置调整: + +```typescript +const eloService = new EloService(repository, calculationService) + +// 更新配置 +eloService.updateEloConfig({ + initialElo: 1500, + kFactor: { + newbie: 40, + normal: 20, + veteran: 10 + }, + minElo: 200, + maxElo: 2800 +}) +``` + +## 监控和日志 + +### 日志记录 + +- ELO更新详细日志 +- 计算过程追踪 +- 错误处理和恢复 + +### 性能监控 + +- 数据库查询优化 +- 批量更新性能 +- API响应时间 + +## 未来扩展 + +### 可能的增强功能 + +1. **季度重置** - 定期重置ELO评级 +2. **衰减机制** - 长期不活跃玩家ELO衰减 +3. **匹配优化** - 基于ELO的智能匹配 +4. **成就系统** - ELO里程碑奖励 +5. **数据分析** - ELO分布统计和趋势分析 + +### 扩展新规则集 + +添加新规则集只需: + +1. 在规则系统中定义新规则集 +2. ELO系统自动支持(无需代码修改) +3. 玩家首次游戏时自动创建ELO记录 + +## 故障排除 + +### 常见问题 + +1. **ELO记录不存在** + - 系统会自动创建初始记录 + - 检查规则集ID是否正确 + +2. **ELO更新失败** + - 检查数据库连接 + - 查看服务器日志 + - 验证战斗数据完整性 + +3. **API响应错误** + - 确认数据库迁移已执行 + - 检查API路由注册 + - 验证请求参数格式 + +### 调试工具 + +```bash +# 测试ELO计算 +npx tsx packages/server/src/test-elo.ts + +# 检查数据库表 +SELECT * FROM player_elo_ratings LIMIT 10; + +# 查看API状态 +curl "http://localhost:8102/api/v1/elo/config" +``` \ No newline at end of file diff --git a/public/assets/210.xml b/public/assets/210.xml index 036714392..dc2570f48 100644 --- a/public/assets/210.xml +++ b/public/assets/210.xml @@ -8,6 +8,7 @@ des 鼠标移上去的提示