Compare commits

...

808 Commits

Author SHA1 Message Date
昔念
4abd179a23 Switch Woodpecker SSH plugin image to Huawei Cloud mirror
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-18 13:54:35 +08:00
昔念
a3e88c7357 Switch Woodpecker SSH step to ghcr drone-ssh image
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-18 13:33:18 +08:00
昔念
4e1a9a815f Switch Woodpecker SSH step to plugins/ssh image
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-18 13:22:44 +08:00
昔念
de3ae0bca2 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-18 13:09:12 +08:00
昔念
b1ff4d3a2a Switch Woodpecker Go image to DaoCloud mirror
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-18 12:44:59 +08:00
昔念
24b52e14c3 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-18 12:35:40 +08:00
昔念
2b92baf530 Use mirror image sources in Woodpecker pipeline 2026-04-18 12:24:34 +08:00
昔念
819d5f667b Set git-sync mode to push for force overwrite sync 2026-04-18 12:13:43 +08:00
昔念
de6c700bb3 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-18 01:30:48 +08:00
昔念
3232efd05a 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-17 00:48:43 +08:00
昔念
0c79fee8af 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-17 00:35:17 +08:00
昔念
3d77e146e9 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-16 23:49:28 +08:00
xinian
a43a25c610 test: cover legacy round broadcast handling
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-16 10:25:56 +08:00
xinian
3cfde577eb test: add pet fusion transaction coverage
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-16 09:21:39 +08:00
xinian
85f9c02ced fix: correct self-destruct mutual KO handling
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-16 09:21:02 +08:00
昔念
9f7fd83626 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-15 22:42:56 +08:00
昔念
ee8b0a2182 Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-15 22:17:08 +08:00
昔念
6e95e014fa 1 2026-04-15 22:16:56 +08:00
xinian
61a135b3a7 fix: 修复宠物升级经验显示与动态结算
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-15 15:34:16 +08:00
xinian
5a81534e84 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-15 15:07:27 +08:00
xinian
523d835ac0 boss属性丢失,全部被限制属性不能超越,应该给ai和玩家施加不同的getinfo方法
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
道具扣除判断,因为判断扣完大于0,所以导致只剩一个的时候,道具成功使用但是不会扣除数量
2026-04-15 14:44:46 +08:00
昔念
5a7e20efec 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-15 03:46:55 +08:00
昔念
5f47bf0589 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-15 03:22:59 +08:00
昔念
a58ef20fab 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-15 00:19:21 +08:00
昔念
3999f34f77 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-15 00:17:06 +08:00
昔念
6f51a2e349 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-15 00:07:36 +08:00
xinian
de755f8fd0 fix: 修正效果33为消除敌方阵营所有强化
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-14 16:26:05 +08:00
xinian
803aa71771 更新说明
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-14 15:55:28 +08:00
xinian
4a77066d08 refactor: 重构持续伤害触发时机为回合开始
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-14 15:21:47 +08:00
xinian
c9b5f8569f fix: 修复道具扣除和宠物融合事务处理问题
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-14 13:06:28 +08:00
xinian
ddbfe91d8b fix: 修复扭蛋道具扣除逻辑
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-14 11:06:04 +08:00
昔念
74ac6ce940 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat
2026-04-14 01:00:34 +08:00
昔念
43b0bc2dec ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight_boss): 优化BOSS战斗奖励逻辑并修复宠物等级突破100级限制

重构了handleMapBossFightRewards函数,将奖励逻辑分离到独立的处理函数中,
增加了shouldGrantBossWinBonus条件判断,确保只有满足条件时才发放胜利奖励。

同时修复了宠物等级系统,允许宠物等级突破100级限制但面板属性仍保持100级上限,
改进了经验获取和面板更新逻辑。

fix(item_use): 添加全能性格转化剂使用验证

添加了UniversalNatureItemID常量定义,增加对道具ID和性格配置的有效性验证,
确保只有正确的道具和性格类型才能被使用。

refactor(fight): 统一战斗结束原因处理逻辑

引入normalizeFightOverReason函数来标准化战斗结束原因,
统一了不同模块中的战斗结果映射逻辑,提高了代码一致性。

perf(pet): 优化宠物升级和经验计算性能

移除了等级100的硬性限制,在保证面板属性不超限的前提下允许宠物等级继续增长,
优化了经验分配和面板重新计算的逻辑流程。
```
2026-04-14 00:43:32 +08:00
昔念
b953e7831a ```
feat(fight_boss): 优化BOSS战斗奖励逻辑并修复宠物等级突破100级限制

重构了handleMapBossFightRewards函数,将奖励逻辑分离到独立的处理函数中,
增加了shouldGrantBossWinBonus条件判断,确保只有满足条件时才发放胜利奖励。

同时修复了宠物等级系统,允许宠物等级突破100级限制但面板属性仍保持100级上限,
改进了经验获取和面板更新逻辑。

fix(item
2026-04-14 00:38:50 +08:00
昔念
62d93f65e7 根据提供的code differences信息,由于没有具体的代码变更内容,我将生成一个通用的commit message模板:
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
```
docs(readme): 更新文档说明

- 添加项目使用指南
- 完善API接口说明
- 修正错误的配置示例
```

注意:由于未提供具体的代码差异信息,以上为示例格式。实际使用时请根据具体的代码变更内容填写相应的type、scope、subject和body信息。
2026-04-13 22:53:02 +08:00
昔念
7dfa9c297e ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight): 新增疲惫状态并优化睡眠状态机制

- 实现疲惫状态(StatusTired),仅限制攻击技能,允许属性技能正常使用
- 重构睡眠状态,改为在被攻击且未miss时立即解除,而非技能使用后
- 修复寄生种子效果触发时机,改为回合开始时触发
- 调整寄生效果的目标为技能施放者而非对手

fix(fight): 修正战斗回合逻辑和技能持续时间处理

- 修复Effect2194中状态添加函数调用,使用带时间参数的版本
- 修正Effect13中技能持续时间计算,避免额外减1的问题
- 优化回合处理逻辑,当双方都未出手时跳过动作阶段

refactor(cdk): 重构CDK配置结构和服务器冠名功能

- 将CDKConfig中的CDKType字段重命名为Type以符合GORM映射
- 优化UseServerNamingCDK方法的上下文处理逻辑
- 修复服务器冠名CDK使用时的类型检查条件

feat(player): 完善宠物经验系统和CDK兑换功能

- 增强AddPetExp方法,处理宠物等级达到100级的情况
- 添加查询当前账号有效期内服务器冠名信息的API接口
- 实现服务器服务相关的数据模型和查询方法

fix(task): 任务查询支持启用和未启用状态

- 修改任务服务中的Get、GetDaily、GetWeek方法
- 当启用状态下无结果时,自动查询未启用状态的任务配置
```
2026-04-13 22:27:27 +08:00
昔念
f95fd49efd ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight): 新增疲惫状态并优化睡眠状态机制

- 实现疲惫状态(StatusTired),仅限制攻击技能,允许属性技能正常使用
- 重构睡眠状态,改为在被攻击且未miss时立即解除,而非技能使用后
- 修复寄生种子效果触发时机,改为回合开始时触发
- 调整寄生效果的目标为技能施放者而非
2026-04-13 21:06:45 +08:00
昔念
ce1a2a3588 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(xmlres): 使用rawFlexibleString替换字符串类型以支持灵活解析

- 将EffectArg结构体中的SideEffectArg字段类型从string改为rawFlexibleString
- 将Move结构体中的Name字段类型从string改为rawFlexibleString,并更新反序列化逻辑
- 统一配置文件解析方式,移除磁盘回退机制并简化readConfigContent函数
- 移除不再使用的导入包和变量

fix(fight): 修复战斗系统中的空技能和无效数据问题

- 在collectAttackValues函数中过滤掉SkillID为0的攻击值
- 添加检查避免发送空的攻击信息到客户端
- 移除输入模块中未使用的捕捉逻辑

refactor(middleware): 重构中间件配置并添加CDK权限控制

- 简化middleware.go文件结构
- 为CDK相关接口添加适当的权限中间件
- 优化服务器代理配置

feat(player): 移除宠物捕捉状态字段

- 从ReadyFightPetInfo结构体中移除IsCapture字段
- 简化宠物准备信息的数据结构
```
2026-04-13 11:34:28 +08:00
昔念
3739c2a6f9 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(xmlres): 使用rawFlexibleString替换字符串类型以支持灵活解析

- 将EffectArg结构体中的SideEffectArg字段类型从string改为rawFlexibleString
- 将Move结构体中的Name字段类型从string改为rawFlexibleString,并更新反序列化逻辑
- 统一配置文件解析方式,移除磁盘回退机制并简化readConfigContent函数
- 移除不再使用的导入包和变量

fix(fight): 修复战斗系统中的空技能和无效数据问题

- 在
2026-04-13 11:28:30 +08:00
昔念
eca7dd86e1 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(fight): 修复单输入战斗中效果处理逻辑错误

- 在Effect201的OnSkill方法中调整了多输入战斗检查的位置,
  确保单输入战斗中的单目标效果被正确忽略

- 添加了针对单输入战斗中单目标效果的测试用例

- 移除了重复的多输入战斗检查代码

feat(fight): 添加战斗初始化时捕获标识设置功能

- 在initfightready函数中添加对CanCapture字段的处理
  将玩家的捕获能力信息传递到战斗准备信息中

- 在ReadyFightPetInfo结构体中添加IsCapture字段用于
  标识宠物是否为捕获类型

refactor(fight): 调整战斗初始化顺序确保数据一致性

- 将ReadyInfo初始化移到绑定输入上下文之后执行
  确保团队视图链接完成后再进行准备信息构建

fix(player): 增加宠物血量检查避免无效匹配

- 在玩家匹配检测中增加首只宠物血量检查
  当首只宠物血量为0时不参与匹配以防止异常情况
```
2026-04-13 10:21:13 +08:00
昔念
e161e3626f ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(fight): 修复单输入战斗中效果处理逻辑错误

- 在Effect201的OnSkill方法中调整了多输入战斗检查的位置,
  确保单输入战斗中的单目标效果被正确忽略

- 添加了针对单输入战斗中单目标效果的测试用例

- 移除了重复的多输入战斗检查代码

feat(fight): 添加战斗初始化时捕获标识
2026-04-13 09:59:09 +08:00
昔念
e1a994ba11 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight): 添加效果工厂模式支持以解决闭包变量捕获问题

- 新增initskillFactory函数用于注册效果工厂
- 修改技能效果注册逻辑从直接实例化改为工厂模式
- 解决循环中闭包捕获变量导致的潜在问题

feat(fight): 实现对手输入获取逻辑优化回合处理

- 添加roundOpponentInput方法获取对手输入
- 重构enterturn方法中的先后手逻辑
- 确保攻击方和被攻击
2026-04-12 22:44:13 +08:00
昔念
82bb99d141 ```
refactor(common/rpc): 移除Redis PubSub心跳机制并优化连接管理

移除Redis PubSub连接的心跳保活功能,因为PubSub连接只应负责订阅和接收,
避免在同一连接上并发执行PING操作。更新了ListenFunc和ListenFight函数,
统一代码结构,移除了context包依赖,并添加了相关注释说明。

feat(logic/pet): 新增宠物技能提交功能

新增CommitPetSkills接口用于一次性提交宠物技能学习/替换/排序结果。
实现技能验证、费用计算和状态更新逻辑,包括新技能学习成本和排序费用。
添加isSameUint32Slice辅助函数用于比较技能数组。
```
2026-04-12 19:14:18 +08:00
昔念
f9543a5156 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight): 使用专用函数构建战斗结束数据包

为战斗结束消息创建专用的构建函数,
统一处理战斗结束信息的数据包构建逻辑,
提高代码的一致性和可维护性。

fix(config): 优化数据库查询语句以提高性能

将数组包含操作(@>)替换为 ANY 操作符,
在 Egg、MapPit、PetFusion 等服务中使用更高效
的查询方式
2026-04-12 13:27:39 +08:00
昔念
174830731c ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(xmlres): 添加磁盘配置文件回退机制并支持JSON格式配置

- 新增readConfigContent函数,优先从资源包读取配置,失败时回退到磁盘文件
- 添加diskConfigPath变量存储本地配置路径
- 支持从磁盘读取JSON格式配置文件,增强配置灵活性
- 修改getJson函数增加错误处理和调试日志输出
- 将技能配置从XML格式改为JSON格式,提升数据解析效率
- 初始化时设置默认磁盘配置路径为public/config目录
```
2026-04-12 04:09:19 +08:00
xinian
3a7f593105 fix: 修复 Effect201 在单人战斗中误生效的问题
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-11 22:22:23 +08:00
xinian
f6aa0c3339 feat: 重构任务奖励系统并增加宠物技能和皮肤奖励
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
将任务奖励逻辑重构到单独的文件中,增加对宠物技能和皮肤奖励的支持,优化任务完成处理流程
2026-04-11 19:25:59 +08:00
xinian
ecc483a11a Merge commit '5f5634d999893b23650cba92f2914be2cc895049' 2026-04-11 11:21:58 +00:00
xinian
97c8231b44 编辑文件 help.md
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-11 19:21:32 +08:00
xinian
5f5634d999 perf: 优化战斗逻辑性能与内存分配 2026-04-11 09:39:00 +08:00
昔念
5bfdb5c32b 缺少代码差异信息,无法生成具体的commit message。
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
请提供具体的代码差异内容,我将根据Angular规范为您生成符合要求的中文commit message,包含适当的type、scope、subject和body部分,并确保每行不超过100个字符。
2026-04-11 00:46:43 +08:00
xinian
90f1447d48 refactor: 重构服务器冠名逻辑至独立表
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-10 19:36:59 +08:00
xinian
ee3f25438f 编辑文件 help.md
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-10 14:25:36 +08:00
xinian
2d8969bed2 编辑文件 config.yaml 2026-04-10 12:18:32 +08:00
xinian
fa5d50955d 编辑文件 my-first-workflow.yaml 2026-04-10 12:15:11 +08:00
xinian
6574450489 编辑文件 my-first-workflow.yaml 2026-04-10 12:11:13 +08:00
xinian
0daeb70900 fix: 修复日志格式化字符串错误和任务奖励逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-10 10:28:22 +08:00
昔念
061e4f0c51 Merge branch 'main' of https://cnb.cool/blzing/blazing
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-10 01:55:25 +08:00
昔念
5c76aa7079 ```
feat(fight): 新增团战胜利关闭和超时退出功能

新增 GroupFightWinClose 和 GroupFightTimeoutExit 方法,
用于处理团战胜利关闭和超时退出逻辑,统一调用 QuitFight() 退出战斗。

fix(gold_list): 修复挂单服务中的逻辑错误和潜在异常

修复了 GoldListService 中的多处问题:
- 修正条件判断语句格式
- 添加数据库查询错误检查
- 优化
2026-04-10 01:55:13 +08:00
xinian
b327398448 refactor: 重构任务服务读写逻辑
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-09 22:59:28 +08:00
xinian
d0abb08d5b fix: 修复获取全部/文件读取/ReqShop/ReqShopReqShop 请求错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-09 15:37:48 +08:00
xinian
d2cd601802 更新说明
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-09 13:11:59 +08:00
昔念
487ee0e726 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight): 添加旧组队协议支持并优化战斗系统

- 实现了旧组队协议相关功能,包括GroupReadyFightFinish、GroupUseSkill、
  GroupUseItem、GroupChangePet和GroupEscape方法
- 新增组队战斗相关的入站信息结构体定义
- 实现了组队BOSS战斗逻辑,添加groupBossSlotLimit常量
- 重构宠物技能设置逻辑,调整金币消耗时机
- 优化战斗循环逻辑,添加对无行动槽位的处理
- 改进AI行动逻辑,增加多位置目标选择机制
- 完善捕获系统上下文处理,修复空指针问题
- 添加战斗状态更新和数据同步机制

fix(pet-skill): 修复宠物技能设置中的金币扣除逻辑错误

- 将金币扣除逻辑移到验证之后
- 修正宠物技能数量限制检查的顺序
- 防止重复添加已有技能的情况

refactor(fight): 重构战斗系统代码结构

- 分离新旧组队协议的战斗创建逻辑
- 优化战斗输入验证和处理流程
- 改进战斗循环中的错误处理机制
```
2026-04-09 02:14:09 +08:00
xinian
3b35789b47 feat: 优化CDK服务器冠名逻辑与鉴权
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-08 19:31:44 +08:00
xinian
28b6386963 feat: 新增CDK兑换冠名接口 2026-04-08 18:12:02 +08:00
xinian
1ca0ff344e feat: 新增服务器冠名CDK兑换功能
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-08 15:49:03 +08:00
xinian
9825944efc feat: 添加批量生成CDK功能
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-08 14:17:10 +08:00
xinian
ca96be3905 refactor: 统一战斗报文发送逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-08 12:26:37 +08:00
xinian
4b89588c22 编辑文件 Dockerfile
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-08 10:11:33 +08:00
昔念
0051ac0be8 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight): 添加旧组队协议支持并优化战斗系统

- 实现了旧组队协议相关功能,包括GroupReadyFightFinish、GroupUseSkill、
  GroupUseItem、GroupChangePet和GroupEscape方法
- 新增组队战斗相关的入站信息结构体定义
- 实现了组队BOSS战斗逻辑,添加groupBossSlotLimit常量
- 重构宠物技能设置逻辑,调整金币消耗时机
- 优化战斗循环逻辑,添加对无行动槽位的处理
- 改进AI行动逻辑,增加多位置目标选择
2026-04-08 01:28:55 +08:00
昔念
918cdeac0e Merge branch 'main' of https://cnb.cool/blzing/blazing 2026-04-07 17:26:52 +08:00
xinian
13244313f1 编辑文件 gold_list.go
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-06 12:09:40 +08:00
xinian
4ea9864833 perf: 使用数组代替map优化元素计算性能
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-06 07:16:57 +08:00
xinian
77057e01b6 refactor: 优化命令注册和请求处理逻辑 2026-04-06 07:07:15 +08:00
xinian
f030b61645 fix: 优化TCP/WebSocket协议检测与处理逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-06 06:33:24 +08:00
xinian
5a44154d30 feat: 添加地图节点匹配和战斗等级上限 2026-04-06 05:24:14 +08:00
xinian
a905954b5c feat: 添加宠物训练加成效果
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-06 03:47:17 +08:00
xinian
99748ba41e refactor: 重构奖励发放逻辑并支持签到默认奖励
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-06 03:42:48 +08:00
xinian
40ec827342 refactor: 重构战斗属性和特效应用逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-06 03:11:38 +08:00
xinian
a16a06e389 refactor: 重构签到系统和战斗特效逻辑 2026-04-06 02:51:13 +08:00
xinian
5b37d9493b feat: 实现每日签到功能并优化战斗和道具逻辑 2026-04-06 02:06:11 +08:00
xinian
f433a26a6d refactor: 重构战斗系统为统一动作包结构
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-06 00:58:23 +08:00
xinian
141ba67014 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-05 23:14:54 +08:00
xinian
d83cf365ac 更新说明
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-05 23:13:06 +08:00
xinian
24b463f0aa feat: 增强 Boss 脚本 HookAction 接入能力
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
引入 BossHookActionContext 封装战斗上下文,并支持脚本调用 useSkill 和 switchPet 函数控制战斗行为。
2026-04-05 22:27:38 +08:00
xinian
c021b40fbe feat: 增强踢人逻辑与BOSS脚本支持
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
优化踢人超时处理和僵尸连接清理,支持BOSS动作脚本并增加测试,修复事件匹配与战斗循环中的并发问题。
2026-04-05 21:59:22 +08:00
xinian
36dd93b076 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-05 21:16:01 +08:00
昔念
3ee1283a2c ```
feat(pet): 新增精灵可学习技能查询功能

新增 GetPetLearnableSkills 接口用于查询当前精灵可学习技能(包含等级技能和额外技能ExtSKill),
优化 SetPetSkill 和 SortPetSkills 方法中的技能处理逻辑,提升技能管理和排序的准确性。

同时修复了宠物存储信息查询时缺少参数验证的问题,在管理后台接口中增加 free 参数支持。

BREAKING CHANGE: 管理后台
2026-04-05 12:45:00 +08:00
昔念
c3da3162ee ```
feat(player): 添加玩家断开连接时的安全保存机制

- 实现 SaveOnDisconnect 方法,确保玩家数据在断开连接时安全保存
- 添加并发控制防止重复保存操作,使用互斥锁和完成通道确保一次保存
- 在 socket 关闭事件中改为异步调用 SaveOnDisconnect 避免阻塞
- 添加 panic 恢复机制保护保存过程中的异常情况

refactor(login): 优化登录时的踢人逻辑和超时处理
2026-04-05 11:14:25 +08:00
xinian
37cd641942 refactor: 重构 Prop 字段位置至 baseplayer
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-05 07:45:51 +08:00
xinian
87145579e6 refactor: 移除宠物显示提供者接口
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-05 07:41:50 +08:00
xinian
7ec6381cf1 111 2026-04-05 07:30:55 +08:00
xinian
2ee0cbc094 fix: 修复boss奖励发放逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-05 07:28:39 +08:00
xinian
6510e4e09b refactor: 重构入参类型引用
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-05 07:24:36 +08:00
xinian
34bc35a6b2 feat: 新增游戏协议入站结构体定义
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-05 06:12:32 +08:00
xinian
8352d23164 refactor: 优化精灵背包仓库切换逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-05 06:02:27 +08:00
xinian
e71971d0b4 refactor: 重构宠物背包逻辑到玩家服务
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-05 05:47:25 +08:00
xinian
bceb7965f7 refactor: 重构宠物仓库列表获取逻辑 2026-04-05 05:32:39 +08:00
xinian
c3f052ef30 refactor: 移除 syncBackupPetList 调用和定义 2026-04-05 05:24:55 +08:00
xinian
7d054bbe91 feat: 实现跨服PVP匹配和战斗功能 2026-04-05 05:04:04 +08:00
xinian
102d87da3e 编辑文件 Dockerfile
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-05 02:27:37 +08:00
xinian
78a68148ce chore: update fight logic and effect implementations
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-05 02:25:44 +08:00
xinian
f473c54880 feat: 支持多站位战斗控制绑定模式
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-05 00:03:32 +08:00
xinian
2eba4b7915 feat: 实现乱舞效果并完善战斗输入上下文
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-04 22:39:56 +08:00
xinian
39e1d4c42f refactor: 重构战斗结构体以支持双打模式 2026-04-04 22:13:42 +08:00
昔念
7916f90992 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(player): 移除废弃的宠物列表计数字段

移除了 PlayerInfo 结构体中不再使用的 PetListCount 字段,
该字段为旧登录协议中的精灵列表长度,现已废弃并不再使用。
```
2026-04-04 14:00:36 +08:00
昔念
8ac2833ce2 1 2026-04-04 13:52:57 +08:00
xinian
fbc845526b refactor: 优化控制器初始化和命令解析逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-04 09:33:31 +08:00
xinian
257a979f93 refactor: 重构效果参数处理逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-04 09:26:57 +08:00
xinian
ce7be73e49 fix: 修复 CODEX_BASE_URL 双斜杠问题
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-04 08:59:40 +08:00
xinian
28f2199142 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-04 08:48:25 +08:00
xinian
80cfa0a07e refactor: 替换过时的上下文访问方法
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-04 07:26:08 +08:00
xinian
c89632b409 refactor: 重构效果系统中的上下文引用 2026-04-04 07:22:28 +08:00
xinian
5a5a1db2a3 refactor: 迁移 effect 至新语义上下文 2026-04-04 07:06:00 +08:00
xinian
0ac84a9509 新纪元 2026-04-04 06:27:15 +08:00
xinian
3a9932e307 refactor: 重、、、、 2026-04-04 06:11:01 +08:00
xinian
28d92c1e18 refactor: 重构战斗系统支持多单位多动作 2026-04-04 05:44:02 +08:00
xinian
b62b4af628 style: 清理代码注释和格式 2026-04-04 05:12:30 +08:00
xinian
31d274dd9d feat: 新增战斗效果1630-1634及1609-1624 2026-04-04 04:58:49 +08:00
xinian
9c6f3988de refactor: 重构 CurrentPet 为 CurPet
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-04 04:34:43 +08:00
xinian
6439995434 feat: 支持多精灵战斗位操作 2026-04-04 04:28:04 +08:00
xinian
603c1b5ad3 refactor: 重构战斗效果逻辑实现
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-04 03:14:22 +08:00
xinian
4552af99c7 fix: 修复汇率校验逻辑错误 2026-04-04 01:33:06 +08:00
xinian
8e904e9068 chore: 初始化代码仓库
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-04 01:32:48 +08:00
xinian
ca7222a6c7 feat: 新增战斗效果1568-1572及金豆订单修复
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-04 01:31:39 +08:00
xinian
dabf43aefb feat: 新增战斗效果1568-1572及金豆订单修复 2026-04-04 01:31:25 +08:00
xinian
0f862453cb feat: 实现战斗效果1543-1547和1573-1577
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-04 01:17:25 +08:00
xinian
d8fdc956ef feat: 实现战斗效果1518-1582 2026-04-04 01:04:58 +08:00
xinian
6eb1a589b4 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-03 21:47:00 +08:00
c378d3d5f7 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(admin): 宠物管理功能优化

- 清理宠物控制器中的乱码字符
- 更新宠物获取请求结构体字段注释为英文描述
- 重构变量命名提高代码可读性
- 添加宠物存储信息服务方法
- 优化错误提示信息为英文
- 新增宠物等级查询接口
- 改进宠物购买逻辑验证
```
2026-04-03 12:18:07 +08:00
xinian
1a0e0b405a feat: 添加自然祝福相关技能效果实现
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
实现1383-1387技能效果,包括自然祝福状态下的属性强化、自然祝福层数管理、护盾效果等
2026-04-03 11:19:20 +08:00
xinian
7405aac82d refactor: 合并同类 effect 实现到公共基类
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-03 00:26:59 +08:00
xinian
5204615c28 feat: 新增战斗效果并优化现有逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-03 00:22:05 +08:00
xinian
3c160ef695 feat: 新增精灵仓库管理及战斗特效逻辑 2026-04-03 00:02:51 +08:00
xinian
c19ee7de03 fix: 修复战斗动作提交逻辑 2026-04-03 00:02:34 +08:00
xinian
43881fd988 编辑文件 .cnb.yml
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-02 23:42:37 +08:00
xinian
d86b75408b feat: 新增技能效果1378-1382
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-02 23:32:21 +08:00
xinian
218e23ff81 refactor: 重构战斗系统动作提交和竞技场锁定逻辑
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-02 23:05:18 +08:00
xinian
f221b299cd fix: 并发安全地更新地图计数 2026-04-02 22:38:02 +08:00
xinian
908f9bee98 Merge branch 'main' of https://cnb.cool/blzing/blazing
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-02 16:24:40 +08:00
xinian
c1b18601f3 feat: 实现战斗效果 1413-1437 2026-04-02 16:24:16 +08:00
昔念
8c049bcdcd ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(admin): 宠物管理功能优化

- 清理宠物控制器中的乱码字符
- 更新宠物获取请求结构体字段注释为英文描述
- 重构变量命名提高代码可读性
- 添加宠物存储信息服务方法
- 优化错误提示信息为英文
- 新增宠物等级
2026-04-02 15:00:08 +08:00
昔念
2f220bb863 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-04-02 12:38:51 +08:00
昔念
6aa601bb06 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-02 10:23:07 +08:00
昔念
f810a2ae86 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(pet): 宠物系统重构和功能增强

- 修复战斗boss中effect ID索引错误问题
- 实现宠物仓库和背包管理功能
- 添加宠物列表排序保存功能
- 重构宠物备份列表同步逻辑
- 优化宠物释放和获取逻辑
- 添加宠物背包仓库切换功能
- 修复地图模型广播信息结构问题
- 调整宠物特效数据库查询逻辑
```
2026-04-02 07:49:49 +08:00
昔念
3a13bcc99c ```
feat(game): 实现扭蛋系统批量物品添加功能并优化地图逻辑

- 新增ItemAddBatch方法用于批量添加物品,支持普通道具和特殊道具的分别处理
- 优化扭蛋游戏玩法中的物品添加逻辑,使用新的批量接口提升性能
- 在扭蛋机器人命令中实现完整的物品检查和批量添加流程

refactor(map): 重构地图控制器代码结构并添加注释

- 为EnterMap、LeaveMap、GetMapPlayerList等方法添加中文注释
- 统一地图相关的命名规范,如enter map替换进入地图
- 调整地图玩家列表中BOSS广播命令ID,2021和2022进行对调

refactor(boss): 重构定时BOSS代码并优化注释

- 将原有的中文注释改为英文注释,统一代码风格
- 简化TimeBossRule结构体定义和相关配置
- 优化定时任务注册逻辑,去除冗余的注释和变量

refactor(space): 清理地图空间服务代码注释

- 移除多余的中文注释和说明文字
- 统一代码格式,移除不必要的空行和注释
- 保持原有的天气系统和地图刷怪逻辑不变

fix(role): 修复系统角色权限查询逻辑

- 修改BaseSysRoleService中的查询条件,正确处理管理员权限
- 使用Extend方法替代Where进行复杂的权限判断逻辑
- 确保超级管理员可以访问所有角色,其他用户受限于权限范围

refactor(dict): 添加字典服务批量查询方法

- 新增GetMaxMap方法用于批量获取物品最大持有上限
- 优化数据库查询,减少多次单个查询的开销
- 支持一次请求多个物品的最大数量限制

fix(player): 修复玩家信息保存异常处理

- 将panic方式改为错误日志记录,避免程序崩溃
- 优化Save方法的重试逻辑,统一错误处理方式
- 在本地文件回退时记录详细错误信息

feat(robot): 扩展扭蛋机器人功能

- 添加用户验证和角色创建检查
- 实现批量扭蛋的完整逻辑,支持1-10次抽取
- 集成物品数量检查和批量添加功能
```
2026-04-02 02:33:05 +08:00
昔念
5995f0670c ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(game): 实现扭蛋系统批量物品添加功能并优化地图逻辑

- 新增ItemAddBatch方法用于批量添加物品,支持普通道具和特殊道具的分别处理
- 优化扭蛋游戏玩法中的物品添加逻辑,使用新的批量接口提升性能
- 在扭蛋机器人命令中实现完整的物品检查和批量添加流程

refactor(map): 重构地图控制器代码结构并添加注释

- 为EnterMap、LeaveMap、GetMapPlayerList等方法添加中文注释
- 统一地图相关的命名规范,如enter
2026-04-01 20:10:29 +08:00
昔念
1b6586aedc ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(space): 添加地图模型配置支持并优化BOSS信息结构

添加MapModel字段到MapBossInfo结构体中,用于存储更完整的BOSS模型数据,
修改初始化逻辑从新的MapModel服务获取数据,并更新HP恢复逻辑使用新模型数据。

同时优化MapNode配置表结构,移除冗余字段并调整数据查询逻辑,
将IsBroadcast字段类型改为uint32以
2026-04-01 06:27:03 +08:00
xinian
977fc78cf6 Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-01 02:53:41 +08:00
xinian
2aea283c6b feat: 新增战斗效果1338-1377 2026-04-01 02:53:23 +08:00
昔念
f377068f60 Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-04-01 02:48:27 +08:00
昔念
81c16590d6 ```
feat(pet): 实现宠物展示功能和稀有宠物塔配置

- 添加PetDisplay字段到Player结构体,用于管理宠物展示状态
- 实现PlayerShowPet方法,支持宠物展示逻辑,包括设置展示标识、
  检查宠物存在性并返回相应错误码
- 在Space中添加RefreshUserInfo方法,用于刷新用户信息并应用
  宠物展示信息到SimpleInfo
- 扩展SimpleInfo结构体,添加PetRide字段用于宠物骑乘标识
2026-04-01 02:48:09 +08:00
xinian
1724c06eab 继续消耗全继续 2026-04-01 00:48:42 +08:00
xinian
0a016e9e7f Merge commit '39e64c0bf50536215ca42fd0dda6e0406644d359' 2026-03-31 12:21:44 +00:00
xinian
39e64c0bf5 11 2026-03-31 20:18:54 +08:00
xinian
331337fc51 编辑文件 .cnb.yml
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-31 20:18:31 +08:00
昔念
acfdf5679d Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-31 20:02:39 +08:00
昔念
5675fff48c ```
docs(effect): 移除已完成的效果任务文档

移除effects 876-1061范围内的任务文档,这些effect已经实现或不再需要跟踪。
包括task-053至task-089的多个任务列表,涵盖各种战斗效果的实现说明。
```
2026-03-31 20:02:25 +08:00
xinian
8ef327cbe0 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-31 19:48:23 +08:00
昔念
5b346ef505 Merge branches 'main' and 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-31 10:40:51 +08:00
昔念
1a5996d902 ```
chore(go): 更新Go版本号格式

- 将 common/go.mod 中的 go 版本从 1.23.0 调整为 1.23
-
2026-03-31 10:40:37 +08:00
xinian
6d547ff656 Merge branch 'main' of https://cnb.cool/blzing/blazing
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-31 10:40:25 +08:00
xinian
79a3874d13 feat: 实现效果1072-1081及1173-1177 2026-03-31 10:40:22 +08:00
昔念
6ac3c1b9b7 Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-31 10:03:29 +08:00
昔念
4d39164c86 ```
feat(fight): 完善战斗效果714-718的参数验证和功能实现

- 为Effect714添加armed状态重置逻辑,并增加参数长度验证
- 修复Effect716的子效果创建逻辑,改为通过InitEffect方法创建
- 为Effect717和Effect718添加参数长度验证,防止索引越界
- 重构Effect718的技能命中处理逻辑,统一效果
2026-03-31 10:03:07 +08:00
xinian
a198100547 fix: 修复效果1071的恢复判定及子效果实现逻辑
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-31 10:02:48 +08:00
xinian
3bf07dd5c5 docs: 删除已完成的effect任务文档
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-31 09:38:00 +08:00
xinian
f85228e371 Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-31 09:19:39 +08:00
xinian
ccb4ad8fdf refactor: 重构宠物信息构建逻辑并新增技能效果 2026-03-31 09:19:36 +08:00
昔念
97b2588339 ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
docs(effect): 移除未实现任务文档 task-320

移除 effects 2215-2219 的未实现任务文档,这些 effect 已经被重新评估
或通过其他方式处理,不再需要单独的任务跟踪文档。
```
2026-03-31 09:18:48 +08:00
昔念
89a67655fc ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(effect): 实现effects 1770-1794战斗效果

- 实现Effect 1770: 开启战魂附体效果,免疫对手下1次攻击技能伤害,
  若对手攻击技能PP值为满则额外免疫下1次固定伤害和百分比伤害

- 实现Effect 1771-1779相关战斗效果,包括能力状态反转、固定伤害计算等功能

- 实现Effect 1780-1794系列效果,包含伤害计算、护盾机制、切换限制等功能

- 新增Effect1770Sub、Effect1780Sub、Effect1783Sub、Effect1784Sub等子效果处理

- 添加countTurnEffects辅助函数用于计算回合类效果数量

- 修复Effect1783切换限制逻辑,正确处理回合结束时的状态变更

- 优化Effect1780伤害计算和护盾添加的安全性检查

- 移除对应的任务文档文件,已完成effects 1660-1664、1770-1774、
  1775-1779、1790-1794等任务清单
```
2026-03-31 08:49:22 +08:00
昔念
35434fd1d8 Merge branch 'main' of https://cnb.cool/blzing/blazing
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-31 08:28:51 +08:00
昔念
4cf1bcc07f ```
feat(effect): 实现effects 1770-1794战斗效果

- 实现Effect 1770: 开启战魂附体效果,免疫对手下1次攻击技能伤害,
  若对手攻击技能PP值为满则额外免疫下1次固定伤害和百分比伤害

- 实现Effect 1771-1779相关战斗效果,包括能力状态反转、固定伤害计算等功能

- 实现Effect 1780-1794系列效果,包含伤害计算、护盾机制、切换限制等功能
2026-03-31 08:28:37 +08:00
xinian
f8c301dc51 Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-31 08:20:24 +08:00
xinian
8805e038b6 docs: 移除已完成的待办任务文档 2026-03-31 08:20:22 +08:00
xinian
d6d03a576d refactor: 优化代码结构和逻辑 2026-03-31 08:19:53 +08:00
昔念
1a804f5e19 Merge branch 'main' of https://cnb.cool/blzing/blazing
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-31 06:52:19 +08:00
昔念
07758266d5 ```
feat(effect): 实现多个战斗效果功能

- 实现了effects 2195-2199的功能,包括消除对手回合类效果、概率附加效果、
  护盾/护罩状态下触发的效果等

- 实现了effects 2220-2239的功能,包括攻击特攻最高值转换、闪避与PP归零、
  伤害提升、恢复体力、回合效果管理等功能

- 实现了effects 2270-2294的部分功能修复,调整了精灵
2026-03-31 06:51:54 +08:00
xinian
b4a8048b85 feat: 添加战斗效果800-814及优化道具逻辑
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-31 06:51:40 +08:00
xinian
8552eb61a8 chore: 移除未使用的技能效果描述
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-31 04:39:39 +08:00
xinian
b4463c35e0 feat: 新增效果实现并重构相关逻辑
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-31 04:36:25 +08:00
xinian
79c014f9cd fix: 修正技能效果索引与实现逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-31 03:42:46 +08:00
xinian
7e3f31e267 编辑文件 .cnb.yml
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-31 01:47:32 +08:00
xinian
799fb24f43 编辑文件 .cnb.yml
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-31 01:44:41 +08:00
xinian
e440bd7613 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-31 01:41:49 +08:00
xinian
10f20f453d Merge branch 'main' of https://cnb.cool/blzing/blazing
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-31 01:40:58 +08:00
xinian
92d3c755a5 feat: 实现 764-799 效果及修复编译错误 2026-03-31 01:40:23 +08:00
昔念
b2e6b40e98 Merge branch 'main' of https://cnb.cool/blzing/blazing
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-31 01:00:45 +08:00
昔念
869698892f ```
chore(vscode): 配置vscode runner的CPU核心数为64

配置vscode runner的CPU核心数为64,以提升运行性能
```
2026-03-31 01:00:35 +08:00
xinian
6e2e7ecf29 编辑文件 Dockerfile
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-31 00:55:19 +08:00
昔念
46f2bdcd9a ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
feat(effect): 实现战斗效果1785、2195、2215、2219并更新文档规则

新增战斗效果1785:N回合内每回合使用技能吸取对手最大体力的1/M,
满体力时额外恢复己方所有不在场精灵O点体力

修复战斗效果2195:当对方回合存在效果时才降低技能优先级,
2026-03-31 00:54:29 +08:00
昔念
e037539123 ```
docs(effects): 移除已完成的技能效果任务文档

移除 effects 956-1005、1263-1312、1695-1734 等范围内的未实现技能效果任务文档,
这些任务已经完成实现,相关文档不再需要维护。
```
2026-03-31 00:38:50 +08:00
昔念
ec9f592e44 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(effect): 移除未使用的action包导入

移除了effect/749_753.go文件中未被使用的blazing/logic/service/fight/action包导入,
保持代码整洁性。
```
2026-03-30 21:09:39 +08:00
昔念
23027ccfde ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
feat(game): 完善宠物融合逻辑和野外BOSS战斗机制

- 在玩家挑战野外BOSS时添加新的ger变量控制捕捉状态
- 当BOSS被标记为已捕捉(isCapture==1)时同步设置ger为-1
- 将怪物等级参数改为使用ger变量传递
- 重构宠物融合服务的数据处理逻辑
- 优化融合结果的权重随机算法
- 添加默认融合配置的查询方法
- 统一错误处理和返回值逻辑
```
2026-03-30 21:03:00 +08:00
xinian
023128be25 feat: 新增战斗效果实现
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
新增大量战斗效果逻辑实现,包括效果ID 678-708、734-743、744-748,并更新对应的效果描述映射。
2026-03-30 20:19:41 +08:00
xinian
578b367a9e chore: 更新 Codex API 基础 URL
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-30 15:58:14 +08:00
xinian
0dd60f6f6d 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-30 15:48:54 +08:00
xinian
1d470e305a feat: 新增战斗效果714-733
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-30 15:32:48 +08:00
xinian
7a7fae05ea feat: 新增战斗技能效果648-677
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-30 12:11:44 +08:00
xinian
184fe9f59d feat: 新增战斗效果1253-1262
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-30 01:44:59 +08:00
xinian
c5caab35ae feat: 新增技能效果 1248-1252
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-30 01:13:51 +08:00
xinian
87fdccaddf feat: 实现大量技能效果及战斗逻辑修复
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-30 00:51:18 +08:00
xinian
a7171e9ef4 fix: 修正 effect 642-646 实现逻辑
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-29 21:44:27 +08:00
xinian
0580cb0fef feat: 添加模型自动压缩配置
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-29 19:06:05 +08:00
xinian
66fdc3d189 feat: 实现技能效果 627-672 及 1011-1111
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-29 19:00:08 +08:00
xinian
0875180979 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-29 17:34:15 +08:00
xinian
c40430aaa4 feat: 实现战斗技能效果
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-29 16:38:34 +08:00
xinian
7439c45768 编辑文件 Dockerfile
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-29 15:21:27 +08:00
xinian
561ffdd3dc 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-29 13:21:08 +08:00
xinian
215eb19602 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-29 12:34:25 +08:00
xinian
1a7aa88820 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-29 12:24:26 +08:00
xinian
442963cb1f 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-29 12:08:00 +08:00
xinian
728b8748a9 编辑文件 Dockerfile
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-29 11:18:25 +08:00
xinian
44591d21b1 编辑文件 Dockerfile
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-29 11:14:23 +08:00
xinian
03f05b4e71 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-29 10:04:28 +08:00
xinian
e59a8a5094 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-29 09:46:12 +08:00
xinian
61e41c4de6 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-29 09:31:38 +08:00
昔念
a52d3df5f9 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(logic): 调整PprofWeb启动逻辑位置

将PprofWeb的goroutine启动从debug条件块内移出到条件块外,
确保无论是否为调试模式都能正确启动Pprof服务
```
2026-03-29 02:11:21 +08:00
xinian
03a28c968b feat: 实现失明状态并新增技能效果描述
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-29 01:17:18 +08:00
xinian
856844e845 Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 23:59:33 +08:00
xinian
7376e0e58b feat: 新增一批技能效果实现 2026-03-28 23:59:12 +08:00
昔念
04ddd60d01 ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
feat(database): 添加多个玩家相关表的联合唯一约束

- 为player_talk表添加玩家+挖矿联合唯一索引
- 为player_task表添加玩家+任务联合唯一索引
- 为player_title表添加玩家+称号联合唯一索引
- 为player_pet表添加玩家+精灵联合唯一索引
- 为player_cdk_log表添加玩家+CDK联合唯一索引
- 为player_egg表添加玩家孵蛋联合唯一索引
- 为player_pvp表添加PVP索引
- 为player_sign_in_log表添加签到联合唯一索引
- 为player_room_house表添加房间索引

fix(user-talk): 修复获取聊天配置时的空指针异常

- 在GetTalkCategory方法中添加配置为空的检查
- 当配置为nil时返回系统错误码避免崩溃

refactor(mineral-config): 优化挖矿配置字段注释

- 修改DailyCollectCount字段的数据库注释从"每日可采集次数"改为"可采集次数"

refactor(talk-service): 优化聊天更新逻辑的原子操作

- 重构Update方法中的数据库原子操作逻辑
- 使用InsertIgnore保证记录只插入一次
- 添加player_id条件确保更新操作的准确性
- 改进错误处理和返回值逻辑
```
2026-03-28 23:59:02 +08:00
xinian
875ad668aa feat: 实现战斗效果逻辑和接口重构
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-28 21:57:22 +08:00
xinian
0780eae582 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-28 20:15:55 +08:00
xinian
8ec1165d9a 编辑文件 .gitignore
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-28 20:07:47 +08:00
xinian
194e21f430 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 19:38:13 +08:00
xinian
cefa4d21ce 编辑文件 Dockerfile
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-28 19:11:02 +08:00
xinian
3c32628475 编辑文件 Dockerfile
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-28 18:50:28 +08:00
xinian
5c87d7086c 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 18:09:53 +08:00
xinian
2a1fb9268d 编辑文件 Dockerfile
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-28 17:52:33 +08:00
xinian
0633f6ec7f 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-28 17:51:31 +08:00
xinian
18bf82cc96 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 17:45:47 +08:00
xinian
7e25858d3c 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-28 17:28:16 +08:00
xinian
61ccbc6c62 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 16:56:00 +08:00
xinian
308a2adbae 编辑文件 Dockerfile
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-28 16:23:49 +08:00
xinian
e74c064945 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 15:57:52 +08:00
xinian
9a7bab35e2 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 15:46:42 +08:00
xinian
c25bbfa2df 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 15:30:41 +08:00
xinian
00c3576210 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 15:16:02 +08:00
xinian
9cd56bf038 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 15:03:39 +08:00
xinian
6c53e57517 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 14:42:01 +08:00
xinian
85699b63de 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 14:28:34 +08:00
xinian
bc04e5760c fix: 移除临时的配置文件相关内容
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 14:17:34 +08:00
xinian
fa31635f02 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 14:09:23 +08:00
xinian
42f6779768 refactor: 简化 Dockerfile 并更新 Codex 配置
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 14:01:26 +08:00
xinian
5ffe44a981 chore: 在 Docker 镜像中安装并配置 Codex
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 13:42:16 +08:00
xinian
ab7ebc88ba 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 13:39:42 +08:00
xinian
6af81c90ca 编辑文件 Dockerfile
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-28 13:29:42 +08:00
xinian
076e021752 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 13:22:58 +08:00
xinian
30cf53402c 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 13:07:42 +08:00
xinian
f4f2bf8908 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 13:04:36 +08:00
xinian
20d5b138fc 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 13:00:48 +08:00
xinian
5adea1c1e5 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 12:27:24 +08:00
xinian
11faff1887 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 12:05:25 +08:00
xinian
bfca38e332 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 11:57:25 +08:00
xinian
00a714e6b8 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 11:04:08 +08:00
xinian
6e0fc50b9f 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 10:51:42 +08:00
xinian
334001f844 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 10:40:05 +08:00
xinian
faf9c33500 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 10:06:21 +08:00
xinian
d865b9b202 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 09:57:16 +08:00
xinian
76df53574c 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 09:48:56 +08:00
xinian
bd286662fa 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 09:26:28 +08:00
xinian
68197fea40 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-28 09:17:23 +08:00
昔念
d55c96e383 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(database): 添加多个玩家相关表的联合唯一约束

- 为player_talk表添加玩家+挖矿联合唯一索引
- 为player_task表添加玩家+任务联合唯一索引
- 为player_title表添加玩家+称号联合唯一索引
- 为player_pet表添加玩家+精灵联合唯一索引
- 为player_cdk_log表添加玩家+CDK联合唯一索引
- 为player_egg表添加玩家孵蛋联合唯一索引
- 为player_pvp表添加PVP索引
- 为player_sign_in_log表添加签到联合唯一索引
- 为player_room_house表添加房间索引

fix(user-talk): 修复获取聊天配置
2026-03-28 02:22:15 +08:00
昔念
06091ff42c ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(item_buy): 优化购买逻辑并添加库存限制功能

- 将变量名 is 改为 ok,提高代码可读性
- 移除未使用的变量 addSuccess
- 重构购买成功逻辑,确保物品添加成功后才扣减金币
- 在 talk.go 中修改判断条件,使用 Limit 字段而非 Type 字段
- 在 user_talk.go 中添加 Limit 字段用于限制数量配置
```
2026-03-28 01:46:52 +08:00
xinian
ce0474258a fix: 修复扭蛋币数量显示格式
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-27 18:07:10 +08:00
xinian
58f0f98262 fix: 修复扭蛋参数检查逻辑错误
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-27 18:02:08 +08:00
xinian
0622f4710b feat: 添加道具捕捉后的回调机制
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-27 17:06:18 +08:00
xinian
6767075dcd fix: 修复购买道具数量上限校验逻辑
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-27 16:53:07 +08:00
xinian
ab31947c39 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-27 13:51:57 +08:00
xinian
99af9b6e01 feat: 增加采集限购按日、周、月重置功能
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-27 13:17:42 +08:00
xinian
40411ba84b fix: 修复商品购买和服务相关逻辑问题
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-27 12:56:29 +08:00
xinian
f6745bd2a6 fix: 修复代码逻辑错误
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-27 12:10:58 +08:00
xinian
5e8a09c226 fix: 优化扭蛋提示信息
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-27 11:52:08 +08:00
昔念
ed84e4d2df ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(fight): 重构精灵特效类型枚举结构
2026-03-27 00:28:25 +08:00
昔念
a3740d417d fix: correct typo in error message
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-26 05:45:36 +08:00
昔念
0b2d127faf ```
refactor(pet): 重构宠物服务方法命名和优化数据库操作

- 统一PetService中方法命名规范,将驼峰命名改为标准驼峰格式
- 修复拼写错误:UPdate -> Update, UPdateFree -> UpdateFree等
- 重命名查询方法:PetInfo_One -> PetInfoOneByCatchTime,
  PetInfo_One_ID -> PetInfoOneByID, PetInfo_One_ohter -> PetInfoOneOther
- 优化BuyPet方法中的事务处理逻辑,使用结构体初始化简化代码
- 添加nextCatchTime辅助方法用于生成唯一的捕捉时间戳
- 优化PetAdd方法的实现逻辑,提高代码可读性
- 清理无用注释代码
```
2026-03-26 05:33:40 +08:00
昔念
619e4b50ca ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(pet): 重构宠物服务方法命名和优化数据库操作

- 统一PetService中方法命名规范,将驼峰命名改为标准驼峰格式
- 修复拼写错误:UPdate -> Update, UPdateFree -> UpdateFree等
- 重命名查询方法:PetInfo_One -> PetInfoOneByCatchTime,
  PetInfo_One_ID -> PetInfoOneByID, PetInfo_One_ohter -> PetInfoOneOther
- 优化BuyPet方法中的事务处理逻辑,使用
2026-03-26 04:51:36 +08:00
昔念
5b655c8287 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(pet): 简化UPdate方法的返回逻辑

- 移除不必要的if-else判断语句
- 直接返回err == nil的结果,使代码更简洁
```
2026-03-26 02:36:13 +08:00
昔念
0d2e307021 ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
feat(pet): 修改宠物更新逻辑返回值类型

将PetService.UPdate方法的返回值从error改为bool类型,
并在控制器中相应调整错误处理逻辑,统一使用布尔值判断操作结果。
```
2026-03-26 02:35:43 +08:00
昔念
b60886bae0 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(player): 修复宠物服务中的数据库查询方法

将宠物服务中UPdate和PetInfo_One方法的数据库查询方法从dbm_fix改为dbm,
以确保使用正确的数据库连接进行查询操作。
```
2026-03-26 02:27:05 +08:00
昔念
ec49d28bd4 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(player): 修复宠物信息服务中的数据库查询方法

使用正确的数据库管理器方法 dbm_fix 替换原有的 dbm 方法,
以确保宠物信息查询功能正常工作。
```
2026-03-26 00:31:21 +08:00
昔念
fa0ed36363 ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
fix(pet): 修复宠物更新时的数据库模型方法调用错误

将 s.dbm 方法调用更改为 s.dbm_fix,以确保宠物信息更新功能正常工作
```
2026-03-26 00:29:25 +08:00
昔念
1f614e4904 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(player): 优化宠物信息获取逻辑

在GetPetInfo方法中添加必要的空行以提高代码可读性,
确保代码结构更清晰易维护。
```
2026-03-25 23:29:52 +08:00
昔念
537dfc1be1 根据提供的code differences信息为空的情况,生成一个占位符commit message:
```
docs(changelog): 更新变更日志记录

由于未提供具体的代码差异信息,此提交用于占位和记录变更日志的更新。
```

注意:由于您提供的code differences信息为空,无法生成具体的功能性commit message。请提供实际的代码差异内容以便生成准确的提交信息。
2026-03-25 23:00:18 +08:00
昔念
12c97dbf90 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(robot): 添加敏感词过滤和自动禁言功能

- 引入blazing/cool包用于敏感词检测
- 添加time包用于计算禁言时长
- 实现消息监听器对群聊中的敏感词进行检测
- 当检测到敏感词时自动禁言发送者10分钟
```
2026-03-25 11:34:30 +08:00
昔念
d995a63ff9 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(user): 用户名转换为小写并优化QQ绑定错误消息处理

- 在BindQQ方法中将用户名转换为小写以确保一致性
- 修改bindqq控制器中的错误消息发送方式,使用私信发送绑定结果
- 避免在群聊中显示敏感的绑定错误信息

refactor(robot): 优化扭蛋物品列表展示逻辑

- 引入samber/lo库用于数组去重操作
2026-03-25 11:18:18 +08:00
xinian
6702649cac fix: 修正消息清理逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-25 03:13:06 +08:00
xinian
d980053959 fix: 修复环境变量拼写错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-25 01:34:44 +08:00
xinian
f8b8a87331 fix: 修复日志输出与代码格式错误
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-25 01:22:44 +08:00
xinian
e0a82d57b4 fix: 更新宠物状态时处理错误返回 2026-03-25 00:47:53 +08:00
xinian
21830ddf6a fix: 调整汇率上限阈值
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-24 23:40:51 +08:00
xinian
f00e08764c 编辑文件 boss.go
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-24 12:53:59 +08:00
xinian
b072e61e71 编辑文件 boss.go
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-24 12:49:49 +08:00
xinian
e42f5d8e7e 编辑文件 boss.go
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-24 12:43:26 +08:00
昔念
f1223e471c ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(base): 修复QQ绑定功能中用户名查询条件错误

在BindQQ方法中,将数据库查询条件从"id"字段更正为"username"字段,
确保能够正确根据用户名查找和更新用户信息。
```
2026-03-24 12:18:30 +08:00
昔念
9306f92a35 Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-24 11:56:42 +08:00
昔念
5320dffdd8 ```
feat(user): 添加QQ绑定功能并重构用户登录逻辑

- 在BaseSysUser模型中添加QQ字段,移除密码字段(暂时注释)
- 移除base.bbs.go中的GetUserInfo函数,将其迁移至base_sys_user.go
- 将登录服务中的外部API调用逻辑整合到BaseSysUserService
- 新增BindQQ方法实现QQ号绑定功能,包含重复绑定检查
- 更新GetUserInfo方法,完善用户信息获取和同步逻辑
- 优化导入包,移除未使用的依赖项
```
2026-03-24 11:56:36 +08:00
xinian
745454daea fix: 调整金豆列表排序为升序
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-24 08:17:44 +08:00
xinian
bcb7e9f49c feat: 按汇率降序排列金币列表
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-24 08:17:24 +08:00
xinian
356f7e2e19 refactor: 移除未使用的Select字段
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-24 05:03:31 +08:00
xinian
6f41039c85 feat: 限制挂单汇率
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-24 04:55:50 +08:00
xinian
d0cf39b439 style: 调整代码格式
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-24 04:43:51 +08:00
xinian
d3d0ead712 feat: 优化boss宠物名称显示格式
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-24 03:07:27 +08:00
xinian
75de7bd557 feat: 添加勇者指令和字符串格式化工具
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-24 02:59:06 +08:00
xinian
52db89b390 feat: 添加野怪查询指令及支持服务
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-24 02:21:50 +08:00
xinian
b3cc06cd38 refactor: 优化蛋卡抽奖逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-24 01:52:56 +08:00
xinian
0aba7e7ccb refactor: 简化战斗宠物列表处理逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-24 01:38:02 +08:00
xinian
707142bd49 feat: 新增繁殖和融合查询命令
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-24 01:12:06 +08:00
xinian
133d15e392 refactor: 修复 NewSel700 技能使用方法名
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-24 00:25:32 +08:00
xinian
90d03b3a32 feat: 添加扭蛋物品和扭蛋精灵查询指令
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-23 23:33:25 +08:00
xinian
8ee19aa66f feat: 添加 CDK 查询功能
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-23 22:59:29 +08:00
xinian
e6d28b017b chore: 清理未使用的依赖并更新版本
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-23 22:48:00 +08:00
xinian
f1835f7aec chore: 移除 fumiama 相关依赖 2026-03-23 22:45:16 +08:00
xinian
15764ee027 refactor: 使用标准库替换第三方HTTP客户端并清理依赖
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-23 22:39:24 +08:00
xinian
3ad96070a3 chore: 禁用部分机器人插件
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-23 22:27:22 +08:00
xinian
d8366616e0 feat: 支持单人宠物对战模式
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-23 22:25:07 +08:00
xinian
41a1bfb0c2 fix: 修正PVP对战宠物信息获取错误
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-23 22:21:56 +08:00
xinian
04c9c73ffa build: 升级 Go 版本至 1.23 并更新依赖
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-23 22:13:31 +08:00
xinian
f9892dda8a chore: 调整插件导入顺序
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-23 22:01:00 +08:00
xinian
09d58c1f14 fix: 修正宠物面板计算参数 2026-03-23 22:00:05 +08:00
昔念
705eb31007 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-23 15:01:13 +08:00
xinian
84024aed83 refactor: 重构战斗效果实现逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-23 07:36:20 +08:00
xinian
ce3f1fc02e fix: 修正金豆集市汇率计算与显示格式
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-23 06:41:34 +08:00
xinian
5773b8d182 style: 更新行情列表标题
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-23 06:22:09 +08:00
xinian
e7b64cc669 fix: 修正 simhei 字体路径为绝对路径
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-23 06:14:12 +08:00
xinian
6e268c66f4 feat: 实现金币行情卡片消息格式
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-23 06:05:32 +08:00
xinian
77c404591a feat: 添加赛尔号机器人行情查询功能
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-23 05:32:43 +08:00
xinian
65f696dfc3 refactor: 将 CanFight 方法移至基类
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-23 01:03:49 +08:00
xinian
9380bac839 fix: 修正宠物等级限制逻辑
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-23 01:02:49 +08:00
xinian
6c0c6cafff fix: 修正 JSONB 字段查询变量名
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-23 00:21:22 +08:00
xinian
b51d682646 feat: 支持JSONB字段查询
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-22 23:50:50 +08:00
xinian
4fb5653c28 refactor: 重构战斗初始化传递宠物列表
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-22 23:41:51 +08:00
xinian
61b0d6093f fix: 修正闪光宠物生成逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-21 23:02:33 +08:00
xinian
4ba8fe32c4 feat: 添加BossConfig战胜规则字段并移除MapNode冗余字段
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-21 01:06:59 +08:00
xinian
6717ca5236 fix: 修复重复领取奖励的问题
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-21 01:01:05 +08:00
xinian
1969c01f3e feat: 添加战胜规则配置模块
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-21 00:57:18 +08:00
xinian
d9a99155d9 feat: 实现战斗奖励规则302-316 2026-03-21 00:41:13 +08:00
xinian
9a1a181ecd refactor: 拆分战斗规则文件为独立模块
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-21 00:34:47 +08:00
xinian
afc67b0582 fix: 修正技能563和564的类型初始化
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-21 00:27:12 +08:00
xinian
c049bbd5ac fix: 修复登录空指针及战斗效果
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-21 00:24:34 +08:00
昔念
2dbbc9713c ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(player): 修复PVP控制器数据返回错误

修改了admin/pvp.go文件中的Get方法,将返回数据从alltitile.RankInfo
改为alltitile,确保正确返回完整的PVP信息而不是仅返回排名信息。
```
2026-03-20 18:09:25 +08:00
昔念
05c5f105e9 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(fight): 修复战斗效果数值符号错误

在技能效果501中,修正了属性设置时effectValue的符号问题,
现在正确地应用负值效果。同时调整了代码格式以保持一致性。
```
2026-03-20 15:49:56 +08:00
昔念
90b62b44e4 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(common): 统一Redis连接方式并优化代码结构

- 将 g.Redis("cool").Conn(ctx) 统一改为 Redis.Conn(ctx) 的调用方式
- 在coolconfig中添加ServerList.GetID()方法用于生成服务器唯一标识
- 引入gconv包用于类型转换操作

feat(rpc): 完善ListenFight函数实现集群消息监听

- 新增ListenFight函数,完全对齐ListenFunc
2026-03-20 04:58:23 +08:00
昔念
5657f1e673 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(pet): 修复宠物数量检查逻辑错误

当free参数为2时,应该查询free字段值为2的记录数量,
而不是查询free字段值为1的记录数量,确保精灵数量限制检查正确。
```
2026-03-19 22:25:10 +08:00
昔念
10a82f8e85 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(fight/effect): 重命名DamageLockEx方法为DamageLock

修复方法名错误,将DamageLockEx改为DamageLock以匹配实际功能
```
2026-03-19 21:33:53 +08:00
昔念
75c599b5b3 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(pet): 添加宠物自由状态管理功能

- 在ServiceList和ServicePage方法中添加WhereNot条件支持
- 将宠物销售状态改为自由状态,新增free字段来标识三种状态:
  0为放入仓库,1为放生,2为上架
- 修改PetInfo、UPdateFree、UPdatePrice等方法以支持新的状态逻辑
- 更新BuyPet方法中的验证逻辑
- 调整查询操作中的字段过滤条件
```
2026-03-19 20:54:52 +08:00
昔念
8929a17c97 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(player): 修复金币兑换计算逻辑错误

移除未使用的getgold变量计算,修正兑换金币数量计算公式,
避免因变量未定义导致的潜在运行时错误,确保兑换功能正常工作。
```
2026-03-19 19:01:23 +08:00
昔念
bd5cd9393a ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(player): 优化金币兑换功能

- 在ServiceUpdate方法中返回更新后的数据而不是nil
- 新增DuihuanGold方法用于处理金币兑换逻辑
- 修改黄金列表控制器中的兑换计算逻辑,区分费用和获得金币的计算
- 在添加操作前验证用户金币余额是否充足
- 修正了兑换比例计算和余额检查逻辑
```
2026-03-19 18:36:34 +08:00
xinian
91a20cb034 编辑文件 gold_list.go
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-19 17:47:44 +08:00
昔念
9cc29eec35 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(cool): 添加删除和更新操作的数据影响行数检查

- 在Controller的Delete方法中添加RowsAffected检查,当影响行数为0时返回"not found"错误
- 在Controller的Update方法中添加RowsAffected检查,当影响行数为0时返回"not found"错误
- 修改Service接口定义,将ServiceDelete和ServiceUpdate方法的返回值类型从interface{}改为sql
2026-03-19 17:18:32 +08:00
昔念
24bc74fc87 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
chore(ci): 移除apt源备份步骤

移除了Woodpecker CI配置中不必要的apt源备份操作,
直接清空sources.list文件以确保镜像源配置的纯净性。
```
2026-03-19 15:24:24 +08:00
昔念
164e0d1437 ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
chore(.woodpecker): 更新CI工作流中的apt源配置

为了解决Debian版本兼容性问题,将apt源从bookworm升级到trixie版本,
并优化了源配置流程以提高构建稳定性。
```
2026-03-19 15:22:30 +08:00
昔念
90e0e2d594 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(.woodpecker): 优化Debian镜像源配置

将Debian bookworm镜像源配置改为多行格式,添加了updates和security仓库,
提高配置的可读性和维护性,确保系统能够获取最新的安全更新。
```
2026-03-19 15:17:14 +08:00
昔念
df418cde9c ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
fix(config): 修复宠物概率计算中的数组索引错误

- 修正了EggService.GetResult方法中对pet.Probs数组的索引访问
- 将原来的pet.Probs[len(pet.OutputMons)-1]改为pet.Probs[len(pet.Probs)-1]
- 确保数组边界安全,避免潜在的运行时panic
```
2026-03-19 15:11:29 +08:00
昔念
baf0d1fc06 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(config): 修复宠物蛋系统中概率计算错误

- 修正了EggService中宠物产出概率计算时错误使用的字段名
- 将pet.OutputMons修正为pet.Probs以正确累加等级权重

refactor(player): 优化金币列表服务参数处理逻辑

- 移除未使用的gconv导入包
- 简化ModifyBefore方法中的用户ID验证逻辑
- 统一设置
2026-03-19 15:08:14 +08:00
昔念
b558f46d7a ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(game): 宠物融合系统添加物品消耗异常处理

- 在宠物融合过程中添加物品扣除失败的错误检查
- 当物品不足时返回ErrInsufficientItems错误码

fix(pet): 宠物仓库管理功能增加数据库操作错误处理

- 在宠物释放到仓库和从仓库取出时验证数据库更新结果
- 添加宠物背包切换功能的错误检查机制

feat(fight):
2026-03-19 14:50:11 +08:00
xinian
e2ac5a6325 feat: 增加宠物蛋结果计算和金币检查逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-18 11:14:55 +08:00
昔念
e7098e3777 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(space): 更新地图BOSS生成逻辑并优化天气变化处理

- 修改GetMapPlayerList方法中BOSS信息发送方式为调用GenBoss方法
- 注释掉Space结构体中的IsChange字段,不再使用该标志位
- 调整GenBoss方法参数,添加isfrist参数用于区分首次调用
- 重构定时任务中的GenBoss调用逻辑,改为匿名函数包装广播消息
- 移除GenBoss
2026-03-18 01:22:14 +08:00
昔念
d1d20a4067 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(admin): 添加金豆列表新增功能并增加订单状态字段

- 在GoldListController中添加Add API接口
- 为GoldBeanOrder模型增加Status字段用于标识订单状态
- 状态字段默认值为0表示待处理,1表示已完成
```
2026-03-17 22:49:14 +08:00
昔念
aa6929cd50 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(fight): 修复战斗逻辑中的条件判断错误

- 修复 IsFirst 方法中的缩进问题
- 修正 IsWin 方法中的条件判断逻辑,将 !v.Alive() 改为 v.Alive()
```
2026-03-17 19:41:07 +08:00
昔念
fb32bb3c39 ```
feat(fight): 添加新效果类型574并优化现有战斗逻辑

- 重命名NewSel409结构体的Action_end_ex方法为Skill_Use_ex
- 将effect/523中HP检查改为Alive()方法调用
- 修复selfkill效果中的代码格式问题
- 新增效果类型574:消耗自身全部体力使下次技能必定先手、命中且暴击
- 实现Effect574的ComparePre和ActionStart方法处理先手、命中和暴击逻辑
```
2026-03-17 19:30:40 +08:00
昔念
a3db0c5500 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(player): 改进怪物生成逻辑支持多配置遍历

修改了Monster.go中的GenMonster函数,将原来的随机选择单个配置改为遍历所有配置,
确保每个符合条件的配置都有机会被处理。同时保持了原有的等级范围、特殊属性设置
和NPC战斗处理逻辑。

BREAKING CHANGE: 怪物生成机制从单一随机选择改为配置遍历匹配
```
2026-03-17 18:24:50 +08:00
xinian
376fa5e8af feat: 新增金豆挂单管理模块及优化购买提示
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-17 16:56:55 +08:00
xinian
38f4be1e04 feat: 新增效果577实现
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-17 15:28:16 +08:00
xinian
322d5ea64d feat: 新增战斗技能效果 524-580
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-17 15:25:08 +08:00
xinian
a2e4ec867c feat: 新增技能效果525、530、550、558、565
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-17 14:35:06 +08:00
xinian
a47b35df88 refactor: 优化怪物生成逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-17 14:14:03 +08:00
xinian
e4f2280625 编辑文件 Dockerfile
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-17 14:05:35 +08:00
xinian
4134603ec6 feat: 添加 cnb-openapi-skills 安装步骤
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-17 13:52:33 +08:00
xinian
937ddd0a97 fix: 修复宠物存活状态判定逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
移除 `NotAlive` 字段,改用 `Alive()` 方法通过 HP 判断存活状态,修正相关效果触发逻辑。
2026-03-17 13:34:50 +08:00
xinian
1e37d71878 fix: 修复属性设置逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-17 10:57:11 +08:00
xinian
ed8e3327b4 fix: 修正PVP模型PlayerID类型并优化空值处理
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-17 10:11:10 +08:00
xinian
15ecbcc7de fix: 修正宠物抓捕时间初始化
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-17 10:06:56 +08:00
昔念
ae41e15c1b ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(player): 修复玩家信息服务中的代码格式和类型错误

- 修复了代码中多余的空格导致的格式问题
- 将saveToLocalFile方法的参数类型从*model.Player更正为*model.PlayerInfo
- 更新了FallbackData结构体中PlayerData字段的类型定义
- 修改了玩家ID获取逻辑,从PlayerID改为UserID以匹配实际数据结构
- 简化了Save方法中的
2026-03-17 00:02:21 +08:00
昔念
9538ef2ab7 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(fight/effect): 修复技能回复血量计算错误

将技能回复血量的计算参数从 Args()[1] 改为 Args()[0],
确保正确使用第一个参数进行最大体力值的除法运算。
```
2026-03-16 22:36:20 +08:00
昔念
c07c87718b Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-16 22:30:21 +08:00
昔念
d74652373c ```
refactor(socket): 更新广播和退出逻辑中的类型引用

更新socket服务器中广播功能和退出功能的代码,
将player.Player类型替换为player.ClientData类型,
并相应调整方法调用以适应新的数据结构。

feat(map): 添加LoadOrStore方法支持

在并发安全的swiss map中新增LoadOrStore方法,
提供原子性的加载或存储功能,增强map的操作能力。

refactor(login): 优化登录逻辑中的玩家获取方式

重构登录控制器中获取玩家对象的方式,
直接从
2026-03-16 22:30:12 +08:00
xinian
70e56d6620 fix: 修正效果474参数索引错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-14 20:56:17 +08:00
xinian
65d8468520 fix: 修复战斗模式下的技能和物品使用逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-14 20:02:04 +08:00
xinian
3a39abe9c6 refactor: 重构每日重置逻辑条件判断
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-14 12:09:30 +08:00
xinian
4e1fdd6a22 fix: 修复宠物购买重复订单问题
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-14 10:27:26 +08:00
昔念
30dba8fee3 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(pet): 完善宠物购买功能的验证逻辑

- 添加宠物信息获取和多重验证检查,包括VIP状态、上架状态、存在性等
- 增强数据库事务操作的错误处理机制
- 优化用户余额扣减和宠物删除的事务安全性
- 修复原代码中查询逻辑的位置错误问题
```
2026-03-14 01:36:06 +08:00
昔念
42e315f2f3 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(fight/effect): 修改技能命中方法名为技能使用方法名

- 将 Effect458 结构体中的 SkillHit_ex 方法重命名为 Skill_Use
- 该变更涉及战斗效果模块中ID为458的技能效果实现
```
2026-03-14 01:08:01 +08:00
昔念
5ed58b1316 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(boss): 移除alpacadecimal依赖并修复伤害计算逻辑

移除了NewSeIdx_699中对alpacadecimal的依赖,
修复了技能伤害计算方式,不再使用最小值限制

fix(pet): 修正宠物服务中的代码格式和查询逻辑

修正了UPdatePrice方法中的代码格式问题,
为PetService添加了ListQueryOp配置,增加了免费和非VIP宠物的查询条件
```
2026-03-14 01:02:36 +08:00
昔念
47f806d112 Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-13 22:21:01 +08:00
昔念
001c86b724 ```
feat(player): 优化宠物购买功能的数据查询逻辑

- 修改BuyPet方法中的数据库查询方式,使用事务内的模型查询替代直接服务调用
- 添加了更安全的数据库事务处理机制,确保购买操作的数据一致性
- 重构了宠物信息验证逻辑,提升代码可读性和维护性
```
2026-03-13 22:20:54 +08:00
xinian
5bd32c61c2 feat: 新增效果526和542实现
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-13 21:48:30 +08:00
xinian
23649b2c20 fix: 修正 Effect71 技能逻辑顺序
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-13 21:31:49 +08:00
xinian
49b8b6d301 fix: 修复改价条件判断错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-13 16:06:49 +08:00
xinian
faad50b1df feat: 新增战斗技能效果并修复初始化注册
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-13 16:05:43 +08:00
xinian
0d44de2ea7 fix: 修复PVP赛季数据结构及相关逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-13 12:04:35 +08:00
xinian
b9985bff3b feat: 添加效果1146并优化条件判断逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-13 11:07:46 +08:00
昔念
499a0ba5ab ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(fight): 移除无用依赖并优化技能效果逻辑

- 移除了github.com/alpacahq/alpacadecimal依赖,简化了生命值恢复计算逻辑
- 修复了effect_4_5.go中技能参数索引错误,统一了概率判定和属性设置的参数使用
2026-03-12 21:01:58 +08:00
昔念
c59ff550a7 Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-12 19:34:04 +08:00
昔念
e568de2379 ```
feat(fight): 重构技能效果4和5的实现逻辑

- 移除旧的EffectStat通用结构体
- 为技能4和5创建独立的Effect4和Effect5结构体
- 简化技能触发逻辑,直接使用SideEffectArgs参数
- 技能4现在只影响自身属性,技能5只影响对方属性
- 移除targetOpponent布尔类型判断逻辑

fix(pet): 添加宠物上架数量限制

- 在UPdatePrice方法中增加销售宠物数量检查
- 当is_sale为1时检查
2026-03-12 19:33:56 +08:00
xinian
af09d1ae86 refactor: 优化RPC对战加入与
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-12 14:35:27 +08:00
xinian
7720138290 fix: 修复宠物购买查询用户ID错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-12 11:11:46 +08:00
xinian
3b4862e7a6 编辑文件 effect_413.go
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-12 10:07:28 +08:00
昔念
5e007894ea ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight): 添加技能实体空值检查

为Effect93的Skill_Use方法添加空值检查,当技能实体为空时直接返回true,
避免潜在的空指针引用错误
```
2026-03-12 01:17:02 +08:00
昔念
7f3bfff542 ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
feat(fight): 技能效果93功能优化

- 移除alpacadecimal依赖包导入
- 简化Effect93初始化代码
- 重命名OnSkill方法为Skill_Use以提高语义清晰度
- 直接使用Args()[1]替换SideEffectArgs避免额外参数传递
```
2026-03-12 01:16:26 +08:00
昔念
4c71aa9db1 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(player): 修复宠物等级查询接口返回值初始化问题

- 在PetBagController.Level方法中初始化BaseRes返回值
- 修正PetService.Pet_LEVEL_all查询语句,移除不必要的Fields()调用
- 优化数据库查询条件,确保正确获取等级大于100的宠物数据
```
2026-03-12 01:07:05 +08:00
昔念
bd09013d85 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(fight): 修复技能效果ID配置错误

- 修复NewSeIdx_66.go中空指针检查,避免程序崩溃
- 修正effect/407.go中技能ID从138改为407
- 修正effect/440.go中技能ID从138改为440,并修复类型引用错误
- 修正effect/523.go中属性设置对象错误,从Ctx().Opp改为Ctx().Our
- 修正effect_517.go中技能ID从452改为517
```
2026-03-11 23:38:51 +08:00
昔念
f091748542 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight): 优化战斗效果触发机制

- 移除 EffectDefeatTrigger 的 OnSkill 方法,简化标记逻辑
- 修复 SwitchOut 方法中的判断条件,正确检查对方精灵血量
- 移除多余的 can 字段检查,简化代码流程

feat(pet): 完善宠物购买事务处理

- 添加数据库事务支持,确保购买操作的原子性
- 增加余额检查的安全验证
- 使用原生SQL更新最大时间戳,避免
2026-03-11 22:51:52 +08:00
昔念
9cad3fc4e0 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(base): 修复用户免费金币字段名错误

- 修改 GetFreeGold 方法中返回的字段名从 GoldBean 改为 FreeGold
- 确保方法返回正确的免费金币数值
```
2026-03-11 13:27:28 +08:00
昔念
bef7c994ba ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(base): 兑换金币功能增加免费金币处理

用户兑换金币时同时处理免费金币的转换逻辑,
调用DuihuanFreeGold方法进行免费金币兑换

fix(player): 任务查询条件从task_id 500改为600

修改CanShop方法中的任务ID查询条件,
将硬编码的任务ID从500更正为600以匹配业务需求
```
2026-03-11 12:50:33 +08:00
昔念
a29a8ddec2 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(service): 宠物添加功能增加销售计数参数并优化价格更新逻辑

- 修改PetAdd方法签名,增加salecount参数用于追踪宠物销售次数
- 在多个控制器中统一调用PetAdd方法时传入0作为初始销售次数
- 临时禁用寒流枪活动中的宠物发放功能
- 优化UPdatePrice方法,添加错误处理和价格范围验证逻辑
- 调整宠物购买逻辑,使用免费金币系统并计算递增购买
2026-03-11 12:19:13 +08:00
昔念
46bc05ab29 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(pet): 添加精灵可用性检查

新增Free字段验证,确保购买时精灵确实存在
```
2026-03-11 01:48:46 +08:00
昔念
baa75334ea ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(pet): 修改宠物价格字段类型为uint32

统一宠物价格相关字段的数据类型,将sale_price从float32改为uint32,
以保持数据类型一致性并避免浮点数精度问题。

- 更新controller中PriseReq结构体的Price字段类型
- 修改model中Pet结构体的SalePrice字段类型
- 调整service中UPdatePrice方法的参数类型
```
2026-03-11 01:32:49 +08:00
昔念
6430de9c5d ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
feat(pet): 添加宠物是否可售状态字段

新增is_sale字段用于控制宠物是否可售,
同时修改价格更新接口以支持设置销售状态
```
2026-03-11 01:30:31 +08:00
昔念
b0130f39d5 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(user): 添加申请商店功能

- 新增ReqShop接口用于用户申请成为商店角色
- 验证用户是否满足申请条件(任务完成状态)
- 将符合条件的用户分配到商店角色ID 27

feat(pet): 设置宠物价格最小值限制

- 为宠物价格设置最低5的限制
- 防止价格过低影响游戏平衡

feat(task): 实现商店申请条件检查

- 添加CanShop方法检查用户是否完成特定任务
- 使用位集验证任务ID 500
2026-03-11 00:43:17 +08:00
昔念
ed8b1b71c1 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(pet): 完善宠物购买功能的错误处理和数据验证

- 在控制器层添加错误返回值处理,确保购买宠物操作的错误能够正确传递
- 添加时间验证逻辑,防止使用过期或异常的数据进行购买操作
- 修正金币更新逻辑,确保玩家和系统金币扣除与增加的准确性
- 优化代码结构,增强代码可读性和维护性
```
2026-03-10 23:24:33 +08:00
昔念
fc620d668f ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(player): 宠物购买功能优化

- 使用PetInfo_One_ID方法替代直接数据库查询
- 调整宠物购买时的价格计算逻辑,买入价格从*100调整为*102
- 卖出价格从*100调整为*98,买入扣款也相应调整为*102
- 删除宠物时使用Pet_del方法替代直接数据库删除操作
- 新增PetInfo_One_ID方法
2026-03-10 23:05:43 +08:00
昔念
6792e0e79a ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(pet): 添加宠物交易功能

- 新增ModPrise接口用于修改宠物售价
- 新增BuyPet接口用于购买宠物
- 修改Pet模型中SalePrice字段类型为float32
- 实现宠物购买逻辑,包括价格验证、余额检查和交易处理
- 更新查询条件以支持宠物交易状态筛选
```
2026-03-10 22:20:36 +08:00
昔念
69350bb79e ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(fight): 修复战斗效果类型初始化错误

- 将EffectType.NewSel更改为EffectType.Skill以修正技能效果类型
- 移除effect_415中的多余变量声明,直接使用参数值进行治疗计算

feat(admin): 添加用户金币增加功能

- 新增UserGoldAddReq结构体用于处理金币添加请求
- 实现GoldAdd方法支持管理员为指定用户增加金币

feat(pet):
2026-03-10 20:51:48 +08:00
昔念
ab2ebcd56d ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(fight): 修复战斗效果和属性设置中的边界条件问题

- 修复Effect468中负值处理后提前返回的问题
- 重命名Effect470的SkillHit方法为Skill_Use_ex以匹配实际功能
- 注释掉调试用的println语句
- 在SetProp方法中添加属性值边界检查,确保属性值在-6到6范围内
```
2026-03-10 16:02:38 +08:00
昔念
f58463c0d4 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight_boss): 优化怪物战斗掉落逻辑

- 将随机数生成方法从 grand.Intn(2) + 1 替换为更直观的 grand.N(1, 2)
- 为闪亮怪物奖励添加随机数量机制,从固定1个改为1
2026-03-10 09:36:22 +08:00
昔念
8992132d13 ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
fix(fight): 修复技能效果参数错误和伤害统计问题

- 修复effect_508中参数索引错误,将Args()[1]改为Args()[0]
- 修复effect_508中持续时间设置错误,统一设置为1
- 修复战斗伤害统计错误,将我方受伤统计改为对方受伤统计
```
2026-03-10 09:33:27 +08:00
昔念
939ef29800 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(effect): 移除effect435并重构为selfkill模块

移除独立的effect435实现文件,并将该效果重新实现在selfkill.go中。
effect435功能为牺牲自己使下回合出场精灵首次攻击必定命中和先手。

fix(effect): 修复effect457技能复制逻辑并添加回合结束处理

修复effect457在组队对战中的技能复制逻辑问题,添加deepcopy依赖,
并在回合结束时恢复原始技能状态。

refactor(fight): 调整战斗
2026-03-10 09:17:26 +08:00
昔念
c357773647 ```
refactor(fight/effect): 移除组队对战中无效的技能复制效果

移除了Effect457技能复制功能,该功能在组队对战时无效且不再需要,
简化了战斗效果逻辑。
```
2026-03-10 00:34:28 +08:00
昔念
b8ce414f11 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight): 优化战斗系统技能攻击计算逻辑

- 调整技能攻击时间判断顺序,提前计算总伤害
- 替换随机数生成函数,使用grand包提供更安全的随机数
- 修复暴击计算中的概率判断逻辑,统一使用Meet函数
- 修正技能伤害计算中防御属性获取错误,应使用目标方属性而非攻击方
- 优化基础伤害公式计算顺序,提升性能
- 添加技能伤害调试输出便于问题排查
```
2026-03-10 00:06:02 +08:00
昔念
1fa1ae848d ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight): 实现技能PP消耗Hook机制并优化效果处理

- 在Effect475中添加子效果时通过Ctx().Our.AddEffect正确添加效果
- 删除已废弃的Effect407、Effect440和Effect412效果类型
- 在fightc.go中实现技能使用后的PP消耗Hook机制,支持效果修改PP消耗数量
- 添加HookPP接口方法用于处理技能使用的PP消耗逻辑
- 在SkillInfo中添加Use方法用于实际消耗PP值
```
2026-03-09 23:44:09 +08:00
昔念
0961dc43e3 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(game): 修复寒流枪技能中宠物ID错误

- 将寒流枪技能中第二个宠物的ID从505修正为1905
- 移除了未使用的Effect138效果代码(先出手反弹伤害效果)
```
2026-03-09 22:49:48 +08:00
昔念
ce279cd992 根据提供的code differences信息,我无法看到具体的代码变更内容。由于code differences部分为空白,我将提供一个通用的示例格式:
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
```
docs(readme): 更新文档说明

- 添加了项目使用说明
- 补充了配置项解释
- 修正了错误的示例代码
```

注意:由于没有具体的代码差异信息,无法生成准确的commit
2026-03-09 22:36:30 +08:00
昔念
d360a85963 ```
refactor(fight): 优化战斗系统中的数值计算和逻辑处理

- 将GetProp方法返回类型从int改为alpacadecimal.Decimal,
避免精度丢失问题
- 修改战斗中速度比较逻辑,使用Decimal的Cmp方法进行比较
- 修正BattlePetEntity中属性计算公式,将乘法改为除法
- 调整伤害累加逻辑,修复SumDamage叠加问题
- 更新攻击力和防御力计算,直接使用Decimal数值
- 移除Effect178、Effect501等未使用的技能效果
- 重构回合处理逻辑,调整死亡判断时机和流程
- 添加TrueFirst字段用于正确跟踪实际先手方
```
2026-03-09 20:55:04 +08:00
昔念
d16e079725 ```
fix(fight): 修复效果持续时间设置错误和命中计算参数类型问题

- 修正Effect461中Duration参数从-1改为1,解决持续时间异常问题
- 删除Effect486文件,移除废弃的效果实现
- 修正processSkillAttack中AttackTimeC参数类型,从int转为正确类型
- 在copySkill中添加Accuracy属性复制,解决技能命中丢失问题
- 修正CalculateRealValue函数stat参数类型从int改为int8
- 修正AttackTimeC和GetAccuracy函数level参数类型从int改为int8
- 优化Get
2026-03-09 19:16:17 +08:00
昔念
8cd8c32099 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(fight): 修复技能效果计算中的负值问题

在技能效果35的计算中,当对手属性值为负数时可能导致异常计算结果,
通过使用utils.Max函数确保属性值不小于0来修复此问题。

- 添加blazing/common/utils包导入
- 在物理攻击和特殊攻击的计算中使用utils.Max限制属性值最小为0
```
2026-03-09 18:52:15 +08:00
昔念
994cbb44b8 ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
fix(fight): 修复技能实体属性访问问题

- 修改所有技能实体的ID、Power、CritRate、MustHit、Priority等属性访问方式
  从直接访问改为通过XML字段访问,确保数据一致性

- 更新多个boss技能效果处理逻辑中的属性引用路径

- 移除已废弃的effect/486文件

- 在New
2026-03-09 18:49:51 +08:00
昔念
99ef152434 ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
refactor(fight): 统一技能执行方法命名并修复战斗逻辑错误

- 将多个boss技能结构体中的OnSkill()方法重命名为Skill_Use()以保持一致性
- 修改fightc.go中的战斗回合逻辑,修复attacker和defender的执行顺序错误
- 将Effect126的TurnStart方法改为Skill_Use方法并返回bool值
- 为Effect499添加缺失的方法实现
- 移除effect_124_126.go中未
2026-03-09 17:42:52 +08:00
昔念
36f7aae476 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(fight): 统一技能使用方法命名规范

将多个战斗相关的结构体中的技能使用方法名从不一致的命名
(SkillUseed, OnSkill)统一改为Skill_Use,提高代码一致性。

同时优化了Effect3效果处理逻辑,简化了属性遍历方式,
并修复了先手控制逻辑中的EffectCache处理方式。

BREAKING CHANGE: 技能使用
2026-03-09 17:14:41 +08:00
xinian
f35af82bec refactor: 优化先手判断逻辑,增加IsFirst接口
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-09 12:28:37 +08:00
xinian
f3ada66c11 编辑文件 effect_prop.go
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-09 01:20:57 +08:00
xinian
18fbfcf3cc 编辑文件 85.go
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-09 01:06:44 +08:00
xinian
fd1927f30b 编辑文件 effect_472.go
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-09 00:59:27 +08:00
xinian
68a6f0f0f2 编辑文件 476.go
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-09 00:58:32 +08:00
昔念
86d28f47d1 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(fight): 修复属性值计算逻辑错误

在SetProp方法中,当level为负数时应该减少攻击值而不是增加,
修正了属性值变化的方向错误。
```
2026-03-09 00:40:13 +08:00
昔念
2fd56f9dbf ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(effect): 删除战斗效果back.go文件中的大量效果实现

由于代码重构或功能调整,移除了包含多个战斗效果的back.go文件,
包括效果483、485、188、420、407、491、462、1044、523、177、178、
444、475、496、501、474、440、516、495、457、492、138、469、156、
486、197、505、170、425、412、199、461、5
2026-03-09 00:07:35 +08:00
昔念
c999ac4c8b ```
refactor(fight/effect): 修改战斗效果实现逻辑

移除了多个过时的效果实现,包括:
- 移除效果165:n回合内每回合防御和特防等级+m
- 移除效果184:若对手处于能力提升状态则触发效果
- 移除效果430:消除对手能力强化状态相关逻辑
- 移除效果468:回合开始时处理能力下降状态
- 移除效果471:先出手时免疫异常状态
- 移除效果453:消除对手能力强化
2026-03-09 00:07:19 +08:00
昔念
611b284ade ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
refactor(fight): 优化战斗效果中的属性检测逻辑

通过引入HasPropADD()和HasPropSub()方法来替代循环遍历,
简化了多处战斗效果代码,提高了可读性和性能。

- effect/200.go: 使用HasPropADD()替代循环检测
- effect/418.go: 使用HasPropADD()替代循环检测
- effect/437.go: 使用HasPropADD()替代循环检测
- effect/449.go: 使用HasPropSub()替代循环检测
- effect
2026-03-08 23:43:20 +08:00
昔念
b48578a7ea ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(effect): 移除能力复制操作类型参数并优化属性复制逻辑

移除了 info.AbilityOpType.COPY 操作类型的依赖,简化了能力属性复制的实现方式。
现在直接将对手的正值属性复制到己方,无需指定操作类型参数。同时修正了数组遍历方式,
使用切片语法确保正确的遍历行为。

BREAKING CHANGE: 能力复制相关方法的参数签名发生变化,移除了操作类型参数。
```
2026-03-08 23:36:16 +08:00
昔念
9315fcfa17 ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
refactor(fight): 移除能力操作类型枚举并简化属性设置方法

移除了 info.EnumAbilityOpType 枚举类型及其相关常量定义,
重构了 SetProp 方法调用,不再传递操作类型参数,
通过检查等级正负值来判断是增加还是减少属性,
减少了代码复杂度并统一了属性变更的处理逻辑。
```
2026-03-08 23:24:18 +08:00
昔念
b9739f7b4e ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(fight/effect): 重构技能效果实现

移除独立的Effect112实现,将其整合到selfkill模块中。
通过继承SelfKill结构体来实现技能112的效果,提高代码复用性。
同时修复了原Effect112中的方法命名规范问题,将OnSkill改为Skill_Use。
删除了无用的Effect59 SwitchIn方法。
```
2026-03-08 22:53:14 +08:00
昔念
90f653d3ee ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(effect): 移除未使用的element导入并优化自然敌人判断逻辑

移除了NewSeIdx_14.go中未使用的element包导入,将自然敌人判断逻辑
提取到EffectNode基类中,通过ISNaturalEnemy方法统一处理。
```
2026-03-08 22:43:51 +08:00
xinian
0cff02158b refactor: 统一效果接口方法名为Skill_Use
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 20:07:59 +08:00
xinian
d8beb39e9a fix: 移除战斗结束时的广播调用
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 20:03:09 +08:00
xinian
25e853fa8c refactor: 移除技能使用后的执行逻辑
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-08 20:01:43 +08:00
xinian
84ff2d16c7 feat: 启用技能效果154
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-08 20:00:00 +08:00
xinian
09da1dc253 refactor: 重构战斗技能使用节点
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 19:42:47 +08:00
xinian
ca7eb04f6e refactor: 重命名 ActionEndEx 为 Action_end_ex
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 19:14:42 +08:00
xinian
54e902d1e3 refactor: 重命名 Effect154 技能方法
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 19:09:19 +08:00
xinian
7b77c4c9a3 fix: 修复回血计算覆盖问题
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-08 19:06:58 +08:00
xinian
f803188a4d fix: 修正伤害计算使用Min的错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 18:48:17 +08:00
xinian
53cf3191c2 fix: 修正战斗伤害锁与结束判定逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 18:43:56 +08:00
xinian
c252d6b5f9 fix: 修复伤害锁定逻辑错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 18:25:09 +08:00
xinian
3579b39933 refactor: 优化技能效果参数初始化
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 18:01:41 +08:00
xinian
caaae427d5 chore: 禁用编译产物的压缩步骤
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 17:50:42 +08:00
xinian
c154302af4 fix: 修复Effect初始化空指针问题
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-08 17:48:34 +08:00
xinian
3b1aa450df style: 修复 YAML 格式缩进问题
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 17:24:49 +08:00
xinian
b34b5c5d26 ci: 添加阿里云 Debian 镜像源配置
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-08 17:23:37 +08:00
xinian
c4ea26e443 ci: 修复 upx 安装包名并更新源
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 17:19:06 +08:00
xinian
070ee70949 ci: 修复 upx 安装步骤位置
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-08 17:16:42 +08:00
xinian
e280305728 ci: 安装upx并添加空行
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-08 17:14:34 +08:00
xinian
4c98cdf543 ci: 添加二进制压缩步骤
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-08 17:11:56 +08:00
xinian
8bc6802251 fix: 增加命令超时时间
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 16:59:21 +08:00
xinian
a56f19bd4c fix: 修正使用道具时的物品ID计算错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 16:48:06 +08:00
xinian
069d961585 refactor: 重构效果 150、196、437 实现
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 16:22:54 +08:00
xinian
f6570c7e40 refactor: 提取效果493和497到独立文件
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 16:04:41 +08:00
xinian
aa2c8cfb42 refactor: 重构战斗效果实现到独立文件
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 15:41:16 +08:00
xinian
6c75b106b3 refactor: 重构战斗效果实现
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 15:26:07 +08:00
xinian
a8cbe99873 refactor: 重构技能效果实现
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 14:55:53 +08:00
xinian
986c7f7b83 refactor: 重构战斗效果逻辑至独立文件
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-08 14:07:36 +08:00
xinian
73d7f7f062 feat: 新增战斗效果和修复登录逻辑
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-08 11:22:00 +08:00
xinian
3dd2d40c50 feat: 新增多个战斗技能效果实现
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-08 10:34:23 +08:00
昔念
042a48088d 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-08 01:23:28 +08:00
昔念
ecd63bdea5 ```
fix(space): 修复地图节点配置序列化问题并调整时间间隔

- 为MapBossInfo结构体的Config字段添加`struc:"skip"`标签,
解决配置信息序列化时的问题

- 将Space.Next方法的时间间隔从分钟级别调整为秒级别,
修改随机时间范围为10-30秒,便于测试和调试
```
2026-03-08 01:23:06 +08:00
昔念
e9b9443c06 根据提供的code differences信息,我发现没有具体的代码变更内容。由于缺少实际的代码差异信息,我无法生成准确的commit message。
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
请提供具体的代码变更内容,以便我能够生成符合Angular规范的中文commit message。

如果确实没有代码变更,可以考虑以下模板:

```
chore(git): 更新git配置或文档

- 添加commit message规范说明
- 配置中文提交信息格式要求
```
2026-03-08 00:07:12 +08:00
昔念
53b33dfbc5 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-07 23:59:42 +08:00
昔念
d367f9d1d4 41
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-07 23:54:01 +08:00
昔念
54a4876beb ```
feat(fight): 添加XML配置解析功能用于技能效果映射

- 引入xml和gfile包用于XML文件解析
- 添加utils工具包用于数据转换
- 实现XML配置文件读取和解析逻辑
- 建立技能ID与效果描述的映射关系
- 添加调试输出显示技能效果对应关系
```
2026-03-07 22:49:20 +08:00
昔念
14009f45d6 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight): 添加技能效果调试信息打印

- 引入fmt包用于调试信息输出
- 打印实现效果数量和技能效果不存在数量的统计信息
- 输出技能映射详情用于调试分析
```
2026-03-07 22:32:19 +08:00
昔念
bd3d7eac50 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-07 22:24:54 +08:00
昔念
b9ae17234d ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(boss): 新增技能优先级调整功能

新增ComparePre方法用于比较和调整技能释放优先级,
修复了Boss战斗中的技能执行顺序问题,
同时在login模块中启用战斗测试功能。
```
2026-03-07 22:16:06 +08:00
xinian
a76a7e680e fix: 修复战斗逻辑中的技能结算顺序问题
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-07 20:18:23 +08:00
xinian
2d4ec0e5ba refactor: 重构战斗回合结束逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-07 18:50:51 +08:00
xinian
bf2325e2ef feat: 添加技能效果及宠物性别注释
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-07 15:15:15 +08:00
xinian
de8ce9fc81 feat: 新增多个战斗效果并修复逻辑问题
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-07 14:51:32 +08:00
xinian
02629b6f6c feat: 新增多个技能效果实现
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-07 13:54:42 +08:00
xinian
fe8e2786c2 fix: 修复伤害锁逻辑错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-07 11:44:16 +08:00
xinian
bbaa71f4b2 refactor: 重构宠物初始化逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
提取宠物配置初始化到ConfigBoss方法,简化initplayer代码
2026-03-07 11:30:17 +08:00
xinian
2dab20653f feat: 添加必须道具字段
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-07 10:40:04 +08:00
xinian
7bb0ef856a feat: 新增战斗效果并优化boss逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-07 09:55:06 +08:00
昔念
a192ffa6bc ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(space): 更新空间信息结构支持事件配置

- 替换天气字段为事件配置结构体
- 添加事件匹配逻辑验证天气和时间范围
- 实现事件驱动的BOSS显示控制机制
- 引入Event类型用于管理活动配置
```
2026-03-07 01:30:04 +08:00
昔念
4bb7477147 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight): 添加战斗攻击值获取接口并实现战斗规则系统

添加 GetAttackValue 方法到 FightI 接口,用于获取战斗中的攻击值信息。

新增 RuleI 接口定义战斗规则契约,包括 SetArgs 和 Exec 方法。

重构战斗规则系统:
- 创建 RuleBase 基类提供通用参数存储和基础方法
- 实现 17
2026-03-07 00:26:05 +08:00
昔念
ef7595a218 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(fight): 移除调试打印语句并修复宠物类型验证逻辑

移除了PetKing函数中的调试打印语句,确保不再输出调试信息到控制台。
同时保持了宠物类型验证的核心逻辑不变。

fix(fight): 修正boss技能伤害计算公式

修改了NewSel323的OnSkill方法中伤害计算的公式,
将原来的百分比计算方式调整为正确的血量差值计算方式。

feat(space): 调整空间定时器间隔时间

将Space.Next方法的时间间隔从6-30秒大幅增加到10-30分钟,
以适应实际的游戏节奏需求。

refactor(config): 更新宠物基础配置模型结构

移除了PetBaseConfig中Hp字段的not null约束,
使配置更加灵活。

feat(config): 扩展地图坑位配置支持新功能

为map_pit配置添加了MustTask必做任务字段和DropItemIds掉落物ID列表,
同时为item和pet服务增加了列表查询操作的等值过滤支持。
```
2026-03-06 23:49:20 +08:00
昔念
87ad01bea9 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(fight/boss): 修复技能实体空指针问题并优化必定秒杀效果实现

- 修复NewSeIdx_148中sks映射为空时的panic问题,在访问前进行nil检查并初始化
- 重构NewSeIdx_223必定秒杀效果的实现,将原有的ComparePre和DamageFloor方法
  合并为OnSkill方法,简化逻辑并提高代码可读性
- 更新注释描述,使功能说明更加准确清晰
```
2026-03-06 21:55:54 +08:00
xinian
2461ed1aa4 feat: 添加新技能223并重构技能284伤害处理
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-06 20:15:47 +08:00
xinian
77bb7a7112 fix: 修复伤害计算逻辑错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-06 19:34:58 +08:00
xinian
1645413f8d fix: 修复Boss技能判定逻辑错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-06 18:28:44 +08:00
xinian
26d06c50af fix: 修正Effect71效果计数逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-06 10:40:32 +08:00
xinian
17103cbc9a feat: 添加战斗效果126和284
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-06 10:23:07 +08:00
昔念
24e7f2cd17 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(player): 修复周重置时间字段名不一致问题

- 修正了Player模型中WeekLastResetTime字段的JSON标签,
  从"last_week_reset_time"改为"week_last_reset_time"
- 更新了数据库更新操作中的字段名以保持一致性
- 调整了登录控制器中的调试循环参数,任务ID范围从3
2026-03-05 23:51:07 +08:00
xinian
a4b09a77c3 fix: 修复玩家登录时间重置逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-05 21:22:18 +08:00
xinian
002c0e76c3 feat: 添加非连续技能无效魂印实现
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-05 20:23:06 +08:00
xinian
eb5ea901f4 feat: 新增战斗Boss特性效果实现
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-05 18:04:20 +08:00
xinian
03d93a2fba fix: 修正伤害类型和任务状态判断逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-05 17:40:42 +08:00
xinian
3833fd3884 fix: 修复Scan参数引用错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-05 16:04:28 +08:00
xinian
8efaab48fd docs: 更新任务类型注释
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-05 15:29:18 +08:00
xinian
b80b017d33 fix: 修正任务状态判断与初始化逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-05 15:20:19 +08:00
xinian
2259093790 feat: 支持每周任务重置和查询
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-05 14:56:28 +08:00
xinian
10e1126cd7 fix: 修复宠物竞技奖励计算逻辑错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-05 13:34:06 +08:00
xinian
7e3cfa5875 refactor: 移除未使用的解析函数
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-05 13:21:58 +08:00
xinian
b1ca686e06 refactor: 重构 PVP 匹配逻辑使用模型结构体
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-05 11:21:38 +08:00
昔念
2e7215946b ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(boss): 修复新选择效果ID初始化错误

修正NewSeIdx_183.go和NewSeIdx_184.go中EffectType.NewSel的ID参数,
将错误的83和84分别更正为正确的183和184,确保技能效果正确注册。
```
2026-03-05 01:01:56 +08:00
昔念
ab5ad94d65 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-05 00:49:48 +08:00
昔念
f86dc09a9e ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(common/rpc): 修改Redis订阅主题的生成规则

将Redis订阅主题的生成方式从仅使用端口号改为使用OnlineID和端口号的组合,
通过公式10000*OnlineID+Port来生成唯一的订阅主题,以支持多服务器环境下的
战斗功能区分。
```
2026-03-04 23:41:33 +08:00
昔念
8fdaf91d34 ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
fix(rpc): 修复Redis订阅主题名称错误

修复了战斗系统中Redis订阅主题名称的拼写错误,
将"sun:sendpack"修正为"sun:sendpack:"以确保
消息订阅功能正常工作。
```
2026-03-04 23:40:18 +08:00
昔念
aa53001982 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(cool): 移除Redis监听功能和用户结构体定义

移除ListenFunc函数,该函数提供Redis PubSub监听功能,
包括自动重连和心跳保活机制。同时删除User结构体定义和
相关有序集合变量,这些功能将由rpc模块替代实现。

feat(rpc): 添加对ListenFunc的调用以处理Redis监听

在login模块中
2026-03-04 23:38:21 +08:00
昔念
4751594ee8 ```
feat: 更新战斗系统模型结构和Redis消息处理

- 引入gredis依赖用于Redis消息处理
- 将战斗相关的枚举和结构体从info包迁移到model包
- 更新战斗结束原因、攻击值等类型的引用路径
- 添加新的zset工具包到工作区
- 修改Redis消息处理逻辑以正确解析gredis.Message类型
- 在战斗控制器中统一使用model包下的类型定义
2026-03-04 22:47:21 +08:00
昔念
d14dbde697 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight_pvp_king): 添加宠物属性类型验证逻辑

新增元素类型映射数组,用于验证宠物属性类型匹配,
确保只有正确属性类型的宠物才能参与特定战斗模式。
```
2026-03-04 20:52:39 +08:00
昔念
55a5534777 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight_pvp_king): 新增宠物王者对战模式支持

- 添加战斗类型11的处理逻辑,支持单人模式
- 验证宠物类型与对战类型的匹配性
- 根据战斗结果发放相应奖励物品
- 增加ItemAdd接口用于物品添加功能
- 扩展PetInfo结构体增加战斗类型字段
```
2026-03-04 20:21:02 +08:00
昔念
a48619dde5 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(game): 更新试炼之塔和勇者之塔等级验证逻辑

- 修改fight_塔.go中等级验证条件,添加data.Level != 1的特殊处理
- 防止等级1时的错误验证,允许重新挑战第1层
- 保持原有的最大等级限制逻辑

docs(README): 添加新的pprof性能分析命令示例

- 增加针对端口43892的性能分析命令
- 添加300秒CPU数据采样的HTTP可视化命令
- 为性能调试提供更多示例选项
2026-03-04 17:11:57 +08:00
昔念
bf79c0fd6a ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(fight): 移除调试打印语句并优化阻塞策略

移除了effect_attr.go中的调试println语句,将注释掉相关的日志输出,
同时在pack.go中更新了锁策略,从SleepBlockStrategy切换到
ConditionBlockStrategy,并移除了未使用的time包导入
```
2026-03-04 14:45:52 +08:00
昔念
5874ae270f ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(item-use): 完善玄彩道具使用逻辑并添加宠物闪亮状态修复功能

- 在玄彩道具使用过程中添加闪亮状态修复检查,确保道具使用前宠物状态正常
- 修改FixShiny方法返回布尔值以指示操作是否成功
- 当物品不足时返回ErrItemUnusable错误码
- 调整代码执行顺序,先进行闪亮状态修复再更新物品数量
```
2026-03-04 14:12:48 +08:00
昔念
fc8fc1ed8d ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(socket): 使用bytebufferpool优化内存分配并重构消息处理机制

引入bytebufferpool减少内存分配开销,在ServerEvent.go中修改数据处理逻辑,
将直接的数据拷贝改为使用缓冲池。同时移除原有的消息通道机制,改用lock
2026-03-04 14:00:55 +08:00
昔念
98c4caac68 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(socket): 修复服务器事件处理中的数据引用问题

解决切片共享底层数据导致的潜在内存安全问题,通过深拷贝确保数据独立性,
避免并发访问时的数据竞争风险。
```
2026-03-04 13:16:50 +08:00
昔念
a159838d96 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(login): 修复任务ID打印错误并添加任务完成标记

修复了任务ID在打印时的索引错误,将原来的i改为312+i,
同时取消注释添加了任务314的完成状态设置
```
2026-03-04 13:01:24 +08:00
昔念
3f59f1a353 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(boss-fight): 调整宠物战斗奖励逻辑

修复闪亮怪物奖励物品的位置,将奖励物品发放逻辑从条件判断前移到判断后,
确保只有符合条件的玩家才能获得玄铁奖励。

fix(player-energy): 修复能量时间消耗问题

注释掉EnergyTime的自动减1逻辑,避免玩家能量值异常减少。

refactor(shop-config): 优化商店查询配置

移除商品名称字段查询,只保留remark字段作为关键词搜索,
简化商品表的SELECT语句,提高查询效率。
```
2026-03-04 12:48:49 +08:00
昔念
fb78147035 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(cool): 添加Redis发布功能并实现巅峰赛匹配加入逻辑

新增RedisDo函数用于向Redis频道发布消息,并在巅峰赛场匹配
中添加玩家加入队列的功能。同时修复了socket连接关闭时资源
泄露问题,确保MsgChan正确关闭。

BREAKING CHANGE: 新增的RedisDo函数会直接panic处理错误,
需要调用方注意
2026-03-04 03:22:43 +08:00
昔念
10af34fdad ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(controller): 移除独立的服务模块并将结构体定义内联到控制器中

移除了 egg、leiyi、pet 和 systemtime 独立服务包中的结构体定义,
将所有 C2S 和 S2C 结构体直接定义在相应的控制器文件中,同时更新了
导入路径和服务调用方式,统一使用 common.TomeeHeader 并优化了代码组织结构。

BREAKING CHANGE: 结构体定义从独立的服务包移动到控制器文件内部
2026-03-04 02:24:25 +08:00
昔念
aefef6a456 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(player): 修复玩家ID记录错误

当处理客户端消息时发生panic错误,日志中记录的玩家ID应该是当前客户端数据中的
玩家ID(cd.Player.Info.UserID),而不是错误引用的h.Player.Info.UserID。

这确保了错误日志能够正确关联到实际出错的玩家。
```
2026-03-04 01:38:24 +08:00
昔念
536a0c45c8 ```
refactor(player): 修复消费消息协程中的变量引用错误

修复了 consumeMsg 方法中错误引用变量的问题,将 handleBizLogic 方法中的
变量引用从 'h' 更正为 'cd',确保正确的上下文访问。

同时调整了代码结构,将消息循环处理逻辑移至正确位置,
保证 panic 恢复机制能够正确捕获异常信息。
```
2026-03-04 01:38:12 +08:00
昔念
3ddecce241 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight/boss): 添加技能实体致命一击检查逻辑

在NewSeIdx_409中新增对SkillEntity的空指针检查,
并实现通过Crit字段判断是否为致命一击的逻辑,
只有当Crit等于1且随机判定成功时才执行后续操作
```
2026-03-04 01:33:32 +08:00
昔念
dffd6a63a6 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(player): 优化客户端数据处理机制

重构ClientData的消息处理流程,将OnEvent方法改为非阻塞的通道投递模式,
新增MsgChan用于异步消息传递,避免eventloop阻塞问题。

fix(fight): 修复宠物闪光属性过滤条件

在initplayer方法中增加color.Alpha不为0的判断条件,确保只有有效的
闪光属性才会被添加到宠物信息中。

refactor(socket): 调整服务器事件处理逻辑

移除未使用的Lockfree库依赖,注释掉不再需要的连接关闭资源释放代码,
调整事件处理的工作池提交逻辑。

feat(rpc): 新增Redis发布功能

为RPC_player添加SendPackCmd方法,通过Redis的publish命令实现
跨服数据传输功能。
```
2026-03-04 01:30:40 +08:00
昔念
0aaa4b3ddd ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(login): 优化登录逻辑增加在线状态检查

- 在踢人操作前先检查用户是否在线,避免对不在线用户执行踢人操作
- 调整设置用户在线状态的位置,确保只有在成功获取用户信息后才设置在线状态
- 修复了可能对已离线用户执行踢人操作的问题
```
2026-03-04 00:14:30 +08:00
昔念
1cbe5d60e7 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(player): 启用panic恢复机制处理客户端事件

在ClientData的OnEvent方法中启用defer函数来捕获和处理panic异常,
确保程序在遇到运行时错误时能够正常记录日志并继续运行,提高服务稳定性。

当发生panic时,会记录服务器ID、用户ID和错误信息到日志中。
```
2026-03-03 23:42:23 +08:00
昔念
907517595c ```
fix(socket): 启用客户端连接关闭时LF资源清理

移除注释以确保在客户端断开连接时正确关闭LF资源,
防止资源泄露问题。
```
2026-03-03 23:41:24 +08:00
昔念
0c7fd18bc9 ```
feat(controller): 增强命令注册逻辑并修复试炼塔关卡限制

- 在命令注册时检查重复方法,如果存在则panic提示错误
- 移除CurrentFreshStage和CurrentStage的默认值设置逻辑
- 添加关卡等级验证,确保用户不能挑战超过最大关卡数的关卡
- 修复试炼之塔和勇者之塔的关卡计算逻辑

fix(item): 修复道具使用返回值类型转换问题

- 将ThreeTimes和TwoTimes字段从int32转为uint32返回
- 为能量吸收道具使用函数添加结果结构体初始化

refactor(fight): 清理战斗服务中的注释和字段定义

- 移除C2S_FRESH_CHOICE_FIGHT_LEVEL结构体中冗余的注释说明
- 统一FightOverInfo结构体的格式

fix(item): 修复宠物道具使用的条件判断

- 为道具300790添加DV值大于等于31时不能使用的限制

fix(player): 修复玩家经验加成次数的判断逻辑

- 将TwoTimes和ThreeTimes的判断从不等于0改为大于0
- 将EnergyTime的判断从不等于0改为大于0
- 统一所有次数字段的类型为int32以避免负数问题

chore(admin): 清理无用代码

- 移除未使用的context包导入
- 注释掉未完成的TimeMap接口实现
```
2026-03-03 23:40:21 +08:00
昔念
5caa9a1e4f ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(controller): 增强命令注册逻辑并修复试炼塔关卡限制

- 在命令注册时检查重复方法,如果存在则panic提示错误
- 移除CurrentFreshStage和CurrentStage的默认值设置逻辑
- 添加关卡等级验证,确保用户不能挑战超过最大关卡数的关卡
- 修复试炼之塔和勇者之塔的关卡计算逻辑

fix(item): 修复道具
2026-03-03 19:28:59 +08:00
昔念
103bc0c232 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(gameplay): 实现空间掉落物品系统

- 在Space结构体中添加DropItemIds字段存储掉落物品配置
- 添加GetDrop方法用于随机获取掉落物品
- 将战斗怪物掉落逻辑从地图服务改为使用空间服务
- 修复屏幕会话中断信号配置为^C

BREAKING CHANGE: 掉落物品逻辑从地图服务迁
2026-03-03 14:04:41 +08:00
昔念
834c85f0f1 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(base): 修复screen会话启动时的终端中断信号处理

为screen命令添加stty intr ^_配置,确保在Screen会话中能够正确处理
中断信号,避免程序异常退出问题。
```
2026-03-03 13:47:57 +08:00
昔念
8713f992a1 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(base): 修改部署脚本中的文件名处理逻辑

- 移除随机文件名生成,直接使用原始文件名进行远程路径构建
- 删除时间戳相关逻辑,简化screen会话启动命令
- 添加文件名参数替换,确保日志文件命名正确
```
2026-03-03 13:16:35 +08:00
昔念
ed40364f09 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(base): 修复screen命令中引号转义问题

修复了启动screen会话时的引号转义错误,确保screen_name、exe_path和online_id
变量能够正确传递给bash命令。之前由于引号转义不当导致变量无法正确解析。
```
2026-03-03 13:09:08 +08:00
昔念
57311aaa2e ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(base): 修复screen会话启动时的引号转义问题

修复了启动Screen会话时由于引号转义导致的时间戳变量无法正确传递的问题,
现在通过分离命令参数的方式确保变量能够正确传递到bash环境中。
```
2026-03-03 13:01:53 +08:00
昔念
d4551a8c35 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(space): 重构空间初始化逻辑并修复变量作用域问题

- 将tips和pits的初始化移到循环外部,避免重复创建
- 修复Name赋值位置,确保在正确时机设置空间名称
- 添加条件判断ret.Super != 0,优化性能
- 调整代码结构,提高可读性和维护性
- 移除注释掉的无用代码
```
2026-03-03 01:07:30 +08:00
昔念
6d387f847e ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(item_use): 优化物品列表处理逻辑

- 修改循环方式,使用索引直接赋值替换append操作
- 提升代码性能和内存使用效率

refactor(effect_101): 清理无用注释

- 移除过时的中文注释内容
```
2026-03-03 00:51:06 +08:00
昔念
33ffc5eaac ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(base): 更新服务器启动脚本日志文件命名规则

- 添加时间戳变量定义,格式为 年-月-日_时-分-秒
- 修改日志文件名从固定的 run_{screen_name}.log 改为带时间戳的 run_{timestamp}.log
- 这样可以避免不同启动实例之间的日志文件覆盖问题
```
2026-03-03 00:34:37 +08:00
昔念
7b8251214b ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
fix(player): 修复怪物生成时PitS为空导致的崩溃问题

当玩家切换地图时,PitS可能为nil,此时访问会导致程序崩溃。
添加空值检查以避免此问题。
```
2026-03-03 00:31:36 +08:00
昔念
bbd155b917 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(base): 更新服务器脚本中的文件删除逻辑

删除旧文件的逻辑从单一文件删除改为批量删除以 logic_ 开头的所有文件,
同时保留了删除前的确认检查机制
```
2026-03-03 00:19:08 +08:00
昔念
30a3c8bc5a ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(base): 修改日志文件命名规则

将Screen会话的日志文件名从固定的run.log改为动态的run_{screen_name}.log,
以避免多个实例之间的日志混淆,提高日志管理的清晰度和可维护性。
```
2026-03-03 00:09:03 +08:00
昔念
79d4343cdc ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(common/cool): 更新GetClient函数支持端口参数

更新GetClient函数签名以接收端口参数,并修改客户端映射键的计算方式,
添加GetClientOnly函数用于仅通过uid获取客户端。

fix(common/rpc): 修复RPC调用中的客户端获取方法

将GetClient调用替换为GetClientOnly,确保正确的客户端获取逻辑。

refactor(logic/controller): 重命名Port字段为UID并优化道具列表处理

将Controller结构体中的Port字段重命名为UID以更好地反映其用途,
优化GetUserItemList函数中道具列表的初始化和填充逻辑。

perf(logic): 调整性能分析web服务启动位置

将PprofWeb服务从全局启动移至调试模式下启动,优化服务配置。

refactor(logic/server): 更新服务器UID生成逻辑

修改Maincontroller的UID字段设置方式,使用服务器ID和端口组合生成唯一标识。

refactor(logic/service/player): 移除未使用的导入并优化怪物生成

移除未使用的service导入,优化怪物生成逻辑中的地图数据访问。

feat(logic/service/space): 添加PitS缓存映射并重构空间初始化

添加新的PitS字段
2026-03-02 23:59:15 +08:00
昔念
dab4862f28 Merge branch 'main' of https://cnb.cool/blzing/blazing
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-02 21:56:34 +08:00
xinian
47bc680889 refactor: 将端口和在线ID类型从uint16改为uint32
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-02 18:34:20 +08:00
xinian
ae06d18aa2 feat:
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-02 15:54:21 +08:00
xinian
75dd3af9bd chore: 移除未使用的 Go 开发工具安装
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-02 15:50:59 +08:00
xinian
6b2ca1d510 fix: 修正 Dockerfile 中 impl 安装命令的拼写错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-02 15:44:42 +08:00
昔念
ae534a2e1e ```
feat(item_buy): 优化商品购买限额检查逻辑

- 修改购买黄金商品时的限额验证方式,直接检查单次购买数量是否超过限制
- 调整GoldLog.Cheak方法参数顺序,增加总量控制参数
- 更新错误返回条件,提高限额检查准确性

fix(player_service): 添加时间范围检查功能

- 引入utils工具包用于时间范围验证
- 在IsMatch方法中添加活动开始时间和结束时间的范围检查
- 如果当前时间不在活动时间内则返回匹配失败

refactor(gold_log):
2026-03-02 01:36:16 +08:00
xinian
49c15e26d3 refactor: 调整 QuitSelf 函数中的 goroutine 退出逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-01 19:16:38 +08:00
xinian
911c1d7ec2 编辑文件 item_use.go
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-01 17:26:00 +08:00
xinian
de4617cd6b 编辑文件 player.go
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-01 16:14:59 +08:00
xinian
7a12aa44eb feat: 添加Go开发工具并重构空间服务初始化逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
在Dockerfile中添加多个Go开发工具
将空间服务的初始化逻辑从GetSpace方法提取到独立的init方法
优化代码结构并修复导入顺序
2026-03-01 13:47:56 +08:00
xinian
6758483ab2 fix: 修复获取地图数据时可能的空指针异常
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-01 11:47:39 +08:00
xinian
74ede45d92 fix: 添加宠物进化等级检查并修复降级逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-01 11:25:30 +08:00
xinian
9b344d3753 feat: 重构地图热度信息并添加地图提示功能
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
将地图热度信息从简单的计数器改为包含提示信息的结构体
添加矿物、BOSS、宠物和掉落等提示信息的收集功能
优化地图进入和离开时的计数逻辑
2026-03-01 10:44:31 +08:00
xinian
3656e43d3c feat: 添加地图提示接口并修改时间地图请求方法
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
新增地图提示接口/maptip,同时将/timemap接口的请求方法从POST改为GET
2026-03-01 09:28:00 +08:00
xinian
f1a5b90ca5 refactor: 优化宠物类型变量使用并简化条件判断 2026-03-01 08:59:39 +08:00
xinian
49f8de8661 编辑文件 fight_boss野怪和地图怪.go
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-01 08:15:42 +08:00
xinian
bc16ef6860 编辑文件 fight_boss野怪和地图怪.go
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-01 08:12:49 +08:00
xinian
e7d85133c3 编辑文件 fight_boss野怪和地图怪.go
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-03-01 08:11:03 +08:00
xinian
f434d88f29 编辑文件 随机id生成
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-01 01:26:49 +08:00
xinian
d545093124 编辑文件 修改自增id.sql
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-01 01:16:26 +08:00
xinian
835e816b04 编辑文件 随机id生成
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-01 01:08:43 +08:00
xinian
9cda69c23f 编辑文件 修改自增id.sql
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-01 00:53:55 +08:00
xinian
bcfa601efc 新建文件 随机id生成
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-03-01 00:27:08 +08:00
昔念
01c8c04df6 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(fight): 移除未使用的xmlres导入并优化回血逻辑

移除input包中未使用的xmlres导入,将宠物血量处理逻辑
封装到PetInfo模型中,并添加ModelHP方法来统一处理血量增减
逻辑。

feat(dict): 注释初始化数据相关代码

注释掉字典模块
2026-03-01 00:02:41 +08:00
昔念
24f83c0284 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(data): 添加随机颜色生成功能并重构发光滤镜默认配置

- 引入 grand 包用于生成随机数
- 将 GlowFilterDefault 常量改为 GetDef() 函数,实现动态配置
- 修改默认发光滤镜参数:Alpha从0.8改为0.1,BlurX/BlurY从10改为8,
  Quality从2改为1,Level从1改为2
- 新增 RandomRGBToUint32() 函数生成随机RGB颜色并转换为uint32格式
- 在GetDef()函数中使用随机颜色替代固定颜色值

refactor(config): 优化闪光效果服务中的矩阵生成逻辑

- 移除不必要的变量声明,直接在赋值时调用GenerateRandomOffspringMatrix
2026-02-28 22:31:33 +08:00
昔念
8bc3fd3cb7 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(dict): 添加字典数据压缩加密功能

- 在字典数据接口中集成压缩和加密机制
- 实现Gzip压缩、XOR加密和Base64编码流程
- 新增CompressAndEncrypt和DecryptAndDecompress工具函数
- 在middleware中启用压缩中间件支持
```
2026-02-28 00:21:33 +08:00
昔念
9485c510c0 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
chore(login): 添加调试代码用于任务状态测试

添加了临时的调试代码来测试玩家任务状态,
包括打印任务信息和设置特定任务为完成状态
```
2026-02-27 23:43:00 +08:00
昔念
b6ec530c68 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight): 添加闪亮怪物判断条件并修复物品添加参数格式

- 在战斗逻辑中增加对闪亮怪物的判断,只有闪亮怪物击败后才能获得玄铁
- 修复物品添加时uint32类型转换的参数格式问题
- 添加effect_42技能效果的参数设置方法
- 引入随机数库用于效果持续
2026-02-27 23:29:16 +08:00
昔念
aad5a1b360 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(config): 添加广播节点结构体并重构地图节点配置

- 新增BroadcastNode结构体,包含触发器ID、位置、血量和方向字段
- 将MapNode结构体嵌入BroadcastNode以支持广播节点功能
- 从MapNode中移除重复的TriggerID、Pos、HP字段
- 在Event结构体中新增
2026-02-27 23:13:50 +08:00
xinian
2292de332f refactor: 简化NPC战斗特殊情况的处理逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-27 15:01:02 +08:00
xinian
8dec37a474 refactor: 重构怪物掉落和闪光处理逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
移除OgrePetInfo中与物品和闪光相关的冗余代码,将闪光处理逻辑整合到HandleNPCFightSpecial方法中
新增CanGetXUAN和CanGetItem方法用于判断是否获得特殊物品
添加S2C_GET_BOSS_MONSTER的ADD
2026-02-27 14:48:10 +08:00
xinian
ad43fc8173 refactor: 优化地图节点数据获取逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
将获取地图Boss信息的过滤逻辑从循环中移至GetDataB方法
2026-02-27 11:06:03 +08:00
xinian
bc2f222036 refactor: 重命名DamageLock为DamageLockEx以更准确表达功能
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-27 10:48:49 +08:00
xinian
94ac183131 fix: 修正NewSel113的DamageLock方法接收器类型错误 2026-02-27 10:48:14 +08:00
xinian
f8b948721f refactor: 简化玩家称号获取逻辑,直接返回ID列表
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-27 10:21:49 +08:00
xinian
407d0578ca feat: 添加战斗规则1的基础结构
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-27 09:37:24 +08:00
昔念
741b938587 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-27 01:00:01 +08:00
昔念
a210d653d2 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(shop): 添加商品限购功能并优化购买逻辑

- 修改购买黄金商品接口,添加商品配额检查功能
- 新增月、周、日三种限购类型检查逻辑
- 当商品超出库存或达到购买限制时返回相应错误码
- 移除gold_log表中PlayerID字段的唯一索引约束
- 修复GoldService中的Cheak方法实现,支持多种时间维度限购检查
```
2026-02-27 00:09:23 +08:00
昔念
e4ad1745d4 ```
feat(utils): 添加切片元素计数功能

新增CountSliceElements函数用于统计切片中各元素的出现次数,
返回map[元素]出现次数的映射关系,支持任意可比较类型的切片元素。

fix(config): 调整地图配置模型默认值设置

修改MapPit结构体中的MinLevel和MaxLevel字段设置,
将数据库约束改为非空并设置默认值为1,确保等级范围配置的有效性。

refactor(item): 移除批量更新物品数量的功能

移除UPDATEM方法的实现代码,该方法原本用于批量更新多个物品的数量,
但存在逻辑问题且不再使用,故将其注释掉以避免后续误用。
```
2026-02-26 23:22:31 +08:00
昔念
bfafd5789d ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(utils): 添加切片元素计数功能

新增CountSliceElements函数用于统计切片中各元素的出现次数,
返回map[元素]出现次数的映射关系,支持任意可比较类型的切片元素。

fix(config): 调整地图配置模型默认值设置

修改MapPit结构体中的MinLevel和MaxLevel字段设置,
将数据库约束改为非空并设置默认值为1,确保等级范围配置的有效性。

ref
2026-02-26 22:32:15 +08:00
xinian
21ae004979 feat: 重构怪物生成和NPC战斗处理逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
调整怪物等级处理方式,将固定等级逻辑移至GetLevel方法
优化NPC战斗特殊情况的处理流程
2026-02-26 19:28:02 +08:00
xinian
de297c9904 feat: 为OgrePetInfo添加GetID方法
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-26 19:22:04 +08:00
xinian
fcba504618 feat: 为NewSeIdx_403添加持续伤害效果实现
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
重构持续伤害效果逻辑,移除状态效果的创建与添加,改为直接使用remainingTurns字段记录剩余回合数
在ActionEndEx中处理伤害触发和回合数初始化
新增TurnEnd方法处理每回合剩余回合数递减
2026-02-26 17:53:55 +08:00
xinian
3157c4d41a fix: 修复黑暗传送门打开时boss不存在导致的错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-26 17:21:43 +08:00
xinian
de711bec7a style: 移除 Monster.go 中的多余空行 2026-02-26 17:16:38 +08:00
xinian
1e71ebbd44 fix: 修正给予称号的条件判断
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-26 16:58:25 +08:00
xinian
7ceb2fb3d6 refactor: 重构怪物生成和天气处理逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
将怪物捕捉处理逻辑移至OgrePetInfo结构体
提取天气和Boss生成逻辑为独立方法
移除未使用的导入和冗余代码
2026-02-26 13:38:57 +08:00
xinian
d27112b5a8 feat: 调整怪物生成概率逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-26 12:23:02 +08:00
xinian
e2bfab5131 fix: 修复宠物融合物品消耗逻辑并优化地图Boss信息发送
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
修复宠物融合功能中物品消耗逻辑错误,从批量更新改为逐个更新物品
移除地图玩家列表获取中多余的MapBoss信息发送条件判断
2026-02-26 11:44:52 +08:00
xinian
380796875f feat: 根据NPC战斗特殊处理解除捕捉限制
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-26 11:03:47 +08:00
xinian
ae22e51868 fix: 修复异色精灵生成条件判断错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-26 11:00:11 +08:00
xinian
c921825007 feat: 添加批量更新
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-26 10:40:41 +08:00
xinian
0485fbca43 feat: 添加批量检查物品数量的方法并优化宠物融合逻辑
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
在物品服务中添加CheakItemM方法用于批量检查物品数量
修改宠物融合逻辑使用新方法进行物品检查
2026-02-26 10:24:30 +08:00
昔念
6c61059cfe ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(fight_tower): 修复战斗塔Boss获取逻辑错误

- 修改条件判断从检查boss不为nil改为检查boss[0].BossIds长度不为0
- 修正PetTawor方法中索引越界问题,从boss[0]改为boss[1]
- 在TowerService.Boss方法中添加按tower_level升序排序确保数据一致性
```
2026-02-26 01:02:30 +08:00
昔念
0091e3a8dd ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(game): 修复勇者之塔战斗逻辑错误

将勇者之塔的战斗服务从 Tower600Service 更改为 Tower500Service,
以修正当前关卡难度匹配问题。
```
2026-02-26 00:33:56 +08:00
昔念
dee5278f52 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(boss-fight): 添加boss id验证并修复捕捉机制

- 在玩家挑战boss时添加boss ids
2026-02-26 00:05:43 +08:00
昔念
f7e5880092 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(space): 添加天气变化调试日志并优化地图节点配置

- 在天气变化时添加调试打印语句,便于追踪空间天气变化情况
- 重构地图节点模型,将NodeID重命名为TriggerID以更好地表达其用途
- 添加触发器ID字段用于区分精灵和NPC,支持高ID控制NPC逻辑
- 更新注释说明剧情相关配置的重构计划,通过NPC节点判断类型
- 调整地图怪信息结构体初始化逻辑,直接使用TriggerID作为ID
```
2026-02-25 22:47:16 +08:00
昔念
a3244549f3 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight_boss): 修复Boss挑战功能中的参数传递和效果处理逻辑

- 修复PlayerFightBoss方法中参数名错误(data -> data1)
- 修正BossId获取路径,使用正确的参数引用
- 重构特效处理逻辑,从循环解析改为批量获取
- 添加宠物闪光效果初始化支持

fix(fight_塔): 修复试炼塔相关战斗逻辑错误

- 修正Boss获取逻辑,从单个对象改为数组切片访问
- 调整塔级Boss获取接口
2026-02-25 21:16:36 +08:00
昔念
6af88365c2 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(fight_boss): 重构Boss战斗逻辑并修复捕捉功能

- 移除未使用的taskID和canCapture变量
- 简化Boss数据获取逻辑,移除注释掉的旧代码
- 更新捕捉机制使用bosinfo[0].IsCapture
2026-02-25 19:46:31 +08:00
昔念
dc4835f14c ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(common/utils): 添加时间范围检查工具函数

添加了 IsCurrentTimeInRange 函数用于判断当前时间是否在指定的 HH:MM
时间区间内,支持当前日期的时间比较功能。

refactor(logic/controller): 重构 Boss 挑战逻辑并集成配置服务

- 集成 service 模块替代原有硬编码逻辑
- 重构 PlayerFightBoss 方法,使用新的配置数据结构
- 移除已废弃的 processMonID 函数和相关注释代码

refactor(logic/space): 优化地图 Boss 信息管理和天气系统

- 更新地图 Boss 数据
2026-02-25 19:05:50 +08:00
昔念
7c1540ff6d ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(common/data/xmlres): 注释掉未使用的MonsterMap配置变量

- 将MonsterMap配置变量注释掉,因为当前不再使用该配置
- 相应地注释掉了初始化代码中的MonsterMap赋值逻辑

feat(logic/controller): 统一CanFight方法返回值为ErrorCode

- 修改PlayerFightBoss等战斗控制器中的Can
2026-02-25 16:18:10 +08:00
昔念
5e9ac0bef5 ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
refactor(config): 调整塔配置结构体字段定义

移除了Name和TowerLevel字段中多余的空格,并将BossIds字段从切片类型
改为普通uint32类型,同时更新了数据库标签注释内容。
```
2026-02-25 14:03:27 +08:00
昔念
c00a796203 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(common/utils): 重构concurrent_swiss_map使用官方sync.Map实现

- 替换原有的第三方并发map实现,改为基于标准库sync.Map的封装
- 保持完全的API兼容性,原有配置方法变为无实际作用的占位符
- 优化Range方法实现,移除goroutine/channel开销,避免潜在的死锁风险
- 移除依赖的外部库和
2026-02-25 13:20:38 +08:00
昔念
931809edc4 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(config): 调整MapBoss结构体字段类型

- 将BossMonID字段从[]int切片类型改为int单值类型,并移除相关定义
- 将WinBonusID和FailBonusID字段从[]int切片类型改为int单值类型,
  并设置默认值为0
- 移除多余的空行和格式调整,保持代码整洁
2026-02-24 22:58:58 +08:00
昔念
813eb4c3cd 根据提供的code differences信息,由于没有具体的代码变更内容,我将生成一个通用的提交信息模板:
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
```
docs(readme): 更新文档说明

- 添加了项目使用说明
- 补充了配置项解释
- 优化了文档结构
```
2026-02-24 22:10:49 +08:00
昔念
8a072bd028 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(config): 修改地图坑位配置的数据结构

- 将MapPit结构体中的MapID字段从数组类型[]int32改为单个int32类型
- 更新数据库标签,移除数组类型定义,添加非空约束和索引
- 修改JSON标签注释为"所属地图ID"
- 在服务层的查询操作中添加map_id字段的相等查询支持
```
2026-02-24 19:20:38 +08:00
昔念
0a2ec3af08 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(config): 移除怪物刷新相关代码并优化地图坑位配置

移除 modules/config/controller/admin/monster_refresh.go 中的
TaskInfoController 及其相关路由注册逻辑

移除 modules/config/model/map_monster.go 中的 MonsterRefresh
模型定义及相关常量、方法

重构 modules/config/model/map_pit.go 中的 MapPit 结
2026-02-24 14:11:01 +08:00
昔念
41714fca0b 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-24 13:26:24 +08:00
昔念
f1c162d10f ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(config): 移除MapPit结构体中的PitName字段

移除了MapPit结构体中未使用的PitName字段,该字段包含坑位名称相关信息,
以简化数据模型结构。
```
2026-02-24 13:09:09 +08:00
昔念
fa6132a7d1 Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-24 12:53:16 +08:00
昔念
6c26e448fd ```
refactor(common): 移除未使用的XML解析测试代码

移除test_test.go中未完成的Mapxml函数和login/main.go中的
XML解析注释代码,清理无用的导入包,优化代码结构

BREAKING CHANGE: 删除了modules/config/model/map_moster_node.go
文件中的MapPit相关模型定义
```
2026-02-24 12:53:07 +08:00
xinian
b2189d9501 fix: 修正NPC战斗模式下技能使用逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-24 08:54:21 +08:00
xinian
571c941ae8 feat: 增加踢人超时处理中的玩家信息保存
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
将踢人超时时间从3秒调整为10秒,并在超时处理中保存玩家信息
2026-02-24 07:31:21 +08:00
xinian
05a1900d60 refactor: 移除未使用的lockfree相关代码
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-24 04:22:02 +08:00
xinian
3ac8ab2086 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-24 03:52:32 +08:00
xinian
0f1adffdd5 refactor: 将事件处理逻辑从工作池提交改为直接调用OnEvent
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-24 03:51:25 +08:00
xinian
a5627e6ba1 编辑文件 fight_base.go
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-24 00:36:41 +08:00
xinian
62b7c33d33 fix: 修正捕获怪物时胶囊ID的检查条件
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-24 00:28:40 +08:00
xinian
68ff96ae84 编辑文件 fight_base.go
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-24 00:27:17 +08:00
xinian
467890a60b 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-24 00:12:50 +08:00
xinian
25c9ecdad6 编辑文件 action.go
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-23 22:28:40 +08:00
xinian
b260fff8e8 refactor: 优化玩家信息处理和注册检查逻辑
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
移除不必要的IsReg方法,简化玩家信息获取流程
调整抢先服玩家登录时间检查逻辑
2026-02-23 21:50:57 +08:00
xinian
50a19b2ff9 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-23 21:42:36 +08:00
xinian
215ce98c22 编辑文件 tower_110.go
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-23 15:08:20 +08:00
xinian
eea2e8777f 编辑文件 tower_110.go
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-23 15:06:45 +08:00
昔念
5e1204ab2f 根据提供的code differences信息,我发现没有具体的代码变更内容。由于没有实际的代码差异信息,我无法生成准确的commit message。
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
```
feat: 初始化项目配置

- 添加基本的项目结构文件
- 配置必要的开发环境依赖
- 设置基础的构建和测试脚本
```

**注意:** 由于未提供具体的代码变更详情
2026-02-23 14:51:11 +08:00
昔念
fc5ddcb3f4 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-23 14:45:20 +08:00
昔念
6b316b868c ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(player): 添加称号重复检查逻辑

防止玩家重复获得相同称号,提升系统稳定性

fix(config): 修复boss配置相关代码问题

- 新增BossController的GetList接口
- 优化import语句顺序
- 修正PetBaseConfig中Desc字段为Remark字段
- 移除Tower配置中的TaskIds冗余字段
- 完善ShopConfig字段注释内容
```
2026-02-23 12:39:57 +08:00
昔念
4fff047c4c ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(item): 调整玄彩道具使用数量限制

玄彩道具检查逻辑从 items <= 0 修改为 items < 100,
确保玩家拥有至少100个道具才能使用。

fix(fight): 修复战斗操作通道阻塞问题

添加10秒超时机制到战斗操作通道发送逻辑中,
避免通道满载时的
2026-02-23 10:21:58 +08:00
xinian
c78e8e13c3 feat: 添加金豆消费记录功能
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
在购买金豆商品成功后记录消费信息,并简化消费记录模型字段
2026-02-23 07:52:36 +08:00
xinian
75cfc7bcb1 feat: 添加金豆消费记录功能
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
添加金豆消费记录表和相关服务,用于记录用户金豆消耗明细
在购买逻辑中预留记录消费的注释位置
2026-02-23 07:47:06 +08:00
xinian
84768e3406 feat: 添加炫彩碎片道具处理逻辑并优化闪光信息存储
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
修改道具处理逻辑,新增炫彩碎片道具(300212)的使用功能
优化宠物闪光信息存储方式,改为直接赋值而非追加
2026-02-23 07:30:30 +08:00
xinian
fc0842e388 refactor: 将OgreInfo重命名为OgrePet并更新相关引用
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-23 04:12:12 +08:00
xinian
36cbb5bf81 feat: 为怪物生成和宠物信息添加随机条件
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-23 04:07:23 +08:00
xinian
fc47b7753f fix: 修复异色怪物生成逻辑中的条件判断
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-23 04:04:32 +08:00
xinian
058bae7446 fix: 修正宠物物品ID的偏移量
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-23 04:03:01 +08:00
xinian
029c2b8c6f 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-23 04:01:57 +08:00
xinian
a5e378073c 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-23 03:56:27 +08:00
xinian
a942032bf0 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-23 03:12:32 +08:00
xinian
57ef70911b feat: 为金豆消费记录添加年份和时间字段
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-23 02:41:31 +08:00
xinian
5c3ffc9c32 refactor: 简化商品类型注释并移除冗余字段
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-23 01:33:31 +08:00
xinian
9cf80062a3 fix: 修复获取用户信息时数据复制错误
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-23 01:24:57 +08:00
xinian
cb0e5b0645 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-23 01:24:13 +08:00
xinian
b00d81bf63 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-23 00:57:07 +08:00
xinian
e9915f481e feat: 添加抢先服玩家3天未登录自动降级功能
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
修复数据包处理中UserID为0的问题
优化解包失败时的错误处理格式
添加设置用户部门ID的服务方法
2026-02-23 00:54:47 +08:00
xinian
236965cc63 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-22 23:09:51 +08:00
xinian
91b938fd54 refactor: 重构物品更新逻辑并添加ID为0的检查
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-22 22:57:49 +08:00
xinian
443077bdc3 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-22 22:33:19 +08:00
xinian
932e199622 fix: 修复切片长度校验和内存分配防护问题
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor: 优化战斗循环中的宠物处理逻辑
refactor: 重构物品更新服务使用ORM模型
2026-02-22 21:46:36 +08:00
昔念
3e4b091724 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(item): 修复物品数量判断逻辑

- 将物品数量判断从 `!= 0` 改为 `> 0`,确保只有正数才添加到列表中
- 将物品检查逻辑从 `< 1` 改为 `<= 0`,确保正确处理边界情况
- 在物品更新方法中增加ID为0的防护,避免无效操作
```
2026-02-22 19:33:17 +08:00
昔念
1dc75b529d ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(socket): 优化TCP连接处理性能

- 添加最小可读长度检查,避免无效Peek操作
- 修复数据部分解析逻辑,避免空切片分配

perf(utils): 优化并发哈希映射性能

- 将分段数量调整为CPU核心数
- 重写Range方法,移除channel和goroutine开销
- 添加原子标志控制遍历终止

perf(utils): 优化结构体序列化缓存机制

- 添加sync.Map缓存预处理结果
- 支持结构体、自定义类型、二进制类型分别缓存
- 减少重复反射
2026-02-22 10:59:41 +08:00
昔念
790bc21034 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(config): 添加事件配置和地图绑定功能

- 在BossConfig中添加MapID字段用于绑定地图ID和Event结构体
- 新增Event结构体包含精灵数组、触发时间、天气等事件相关字段
- 在MapPit中添加MapID字段用于绑定地图ID和Event结构体
- 为MapPit添加IsCapture字段标识是否可捕捉
- 更新NewBossConfig和NewMapPit构造函数初始化Event实例
- 注释掉MapNode中的NodeActiveScript字段
```
2026-02-22 10:03:46 +08:00
xinian
f16838a916 refactor: 重构怪物刷新和地图节点配置模型
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
将怪物刷新配置拆分为独立的 map_monster 和 map_moster_node 模型
新增 mapnode 模型用于管理地图节点配置
优化坑位绑定和刷新规则的数据结构
2026-02-22 07:51:37 +08:00
xinian
4e313f02c7 refactor: 将XOR解密逻辑和事件处理移至player服务
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-22 01:01:37 +08:00
xinian
ae764c946a fix: 为战斗动作通道添加非阻塞发送和降级处理
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-22 00:49:50 +08:00
xinian
d159944d37 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-21 22:41:59 +08:00
昔念
e81dc698dd 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-21 19:53:05 +08:00
昔念
31331cccb5 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-21 18:07:46 +08:00
昔念
9011bdbb8a 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-21 17:56:30 +08:00
昔念
f404a92387 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-21 17:41:49 +08:00
昔念
379e3c8ce6 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(config): 添加超时空地图配置和时间地图查询功能

新增IsTimeSpace字段用于标识地图是否为超时空地图,
添加TimeMap API接口支持查询超时空地图配置

perf(socket): 优化XORDecryptU解密函数减少内存分配

基于bytebufferpool实现缓冲区池化,大幅降低高频调用下的
内存分配和GC压力,提升性能表现

refactor(utils): 优化packVal序列化函数提升性能和稳定性

减少反射开销,优化内存拷贝操作,改进错误处理机制,
替换panic为error返回,增强代码健壮性

docs(readme): 添加新的pprof性能分析地址配置
```
2026-02-21 17:32:40 +08:00
昔念
31d9eb3f9e ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(config): 添加超时空地图配置和时间地图查询功能

新增IsTimeSpace字段用于标识地图是否为超时空地图,
添加TimeMap API接口支持查询超时空地图配置

perf(socket): 优化XORDecryptU解密函数减少内存分配

基于bytebufferpool实现缓冲区池化,大幅降低高频调用下的
内存分配和GC压力,提升性能表现

refactor(utils): 优化packVal序列化函数提升性能和稳定性

减少反射开销,
2026-02-21 16:48:42 +08:00
昔念
b536f0974e 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-21 02:07:35 +08:00
昔念
a23662baba 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-21 01:37:21 +08:00
昔念
ff739c430e ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
fix(config): 修复物品获取逻辑错误

在GetEgg方法中修正了物品数量计算的位置,将数据添加操作移至条件判断内部,
确保只有当ItemMaxCount不为0时才将物品信息添加到结果数组中。
```
2026-02-21 00:41:49 +08:00
昔念
fc9697926c ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(config): 修改店铺配置管理API前缀

统一管理后台API路径规范,将店铺配置相关接口前缀从"config/shop"
修改为"/admin/config/shop",以更好地匹配管理后台路由结构。
```
2026-02-20 23:50:57 +08:00
昔念
8e3ed21a3a 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-20 23:33:24 +08:00
昔念
f6b583575a 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-20 22:39:04 +08:00
昔念
922f7c3622 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-20 21:34:27 +08:00
昔念
aeeac8b2ed 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-20 13:29:43 +08:00
昔念
53b18cfd0c 修复大乱斗问题
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-20 09:40:22 +08:00
昔念
58440d5993 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-20 09:01:10 +08:00
昔念
6a65d74ccf 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-20 08:55:05 +08:00
昔念
b52dd783b3 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-20 00:47:15 +08:00
昔念
7996e19900 修改技能选择实际
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-20 00:46:48 +08:00
昔念
1a6870de69 Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-19 23:59:33 +08:00
昔念
2e6d85dfc2 增加青龙 2026-02-19 23:59:25 +08:00
xinian
8f937d5610 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-19 16:02:23 +08:00
xinian
16b6adf1a1 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-19 14:54:36 +08:00
xinian
91c04b1d5e 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-19 13:50:11 +08:00
昔念
5efbb268a5 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
refactor(config): 移除未使用的数据库查询函数

移除了 dbm_nocache 函数,该函数已被注释掉且不再使用,
同时保留了 dbm_nocache_noenable 函数用于无缓存查询操作。
```
2026-02-19 00:32:22 +08:00
昔念
79c5dfbdcb ```
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
fix(fight): 完善boss技能37注释并修复技能48伤害计算逻辑

- 移除NewSeIdx_37.go中TODO注释,完善技能描述
- 修复NewSeIdx_48.go中技能48的伤害减免逻辑,统一使用Ctx().Category()
- 优化modules/config/service/base.go中的缓存配置逻辑
```
2026-02-19 00:31:10 +08:00
昔念
f4438b9000 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(cool): 修改关键词搜索逻辑以支持文本类型匹配

将 WhereOrLike 查询方式改为 WhereOrf,使用 PostgreSQL 的文本类型转换语法,
通过 field::text LIKE 来实现更准确的关键词匹配功能。

fix(config): 为店铺服务添加默认查询条件和字段选择

在 ShopService 中增加 ListQueryOp 配置,设置默认查询条件 is_enable=1
和指定的字段选择列表,优化查询性能并确保数据有效性。
```
2026-02-18 22:43:39 +08:00
昔念
4a5a7727b5 ```
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
feat(boss_fight): 完善宠物捕获和战斗奖励机制

- 在宠物捕获时记录当前地图ID作为CatchMap
- 将经验值奖励改为通过道具系统发放,统一使用ItemAdd方法处理
- 调整EXP奖励的计算方式,移除原有S2C_GET_BOSS_MONSTER中的EXP字段

feat(arena): 优化竞技场对战奖励和EV分配

- 将竞技场胜利奖励的EV值
2026-02-18 22:07:50 +08:00
xinian
1b6ef07ef8 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-18 15:48:52 +08:00
xinian
29d0552b9f feat: 为VIP测试服添加异色宠物权重随机功能
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
修复空间信息获取时的返回限制问题
2026-02-18 15:43:10 +08:00
xinian
b483c30109 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-18 14:33:13 +08:00
xinian
70db1ee68b 编辑文件 Monster.go
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-18 10:45:50 +08:00
xinian
eee65f0f55 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-17 22:56:55 +08:00
xinian
6b1a2c6f99 1 2026-02-17 22:53:45 +08:00
xinian
756edc1cdd 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-17 22:36:18 +08:00
xinian
ac0318b3f4 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-16 15:01:28 +08:00
xinian
2eea724727 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-16 09:44:05 +08:00
xinian
3fb32af89f 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-16 09:16:07 +08:00
xinian
d58c47fd27 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-16 03:33:41 +08:00
xinian
b67dd576e5 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-16 03:02:59 +08:00
xinian
deeb1ccc38 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-16 02:56:01 +08:00
xinian
3d7682732c 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-16 02:45:48 +08:00
xinian
c26ecff9f2 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-16 02:43:17 +08:00
xinian
3d110af911 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-16 02:30:26 +08:00
昔念
e1f910848f 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-14 23:14:43 +08:00
昔念
3b271e7c41 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-14 13:15:10 +08:00
昔念
7a5d7be255 1 2026-02-14 12:49:03 +08:00
昔念
05d427cbea 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-14 11:51:34 +08:00
昔念
0e3269b97e 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-14 09:45:25 +08:00
昔念
50fd54c6d9 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-14 08:01:34 +08:00
昔念
180d735706 11
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-14 07:36:05 +08:00
昔念
24c413030f 实装大乱斗经验
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-14 04:27:57 +08:00
昔念
06b77d598e 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-14 03:05:51 +08:00
昔念
7cdf7a0890 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-14 00:02:28 +08:00
昔念
e5c75f7359 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-13 22:57:05 +08:00
xinian
d258274322 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-13 06:44:16 +08:00
xinian
58157e2d1c refactor: 修复代码格式并调整地图玩家信息获取逻辑
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-13 06:13:56 +08:00
xinian
e47ada7e58 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-13 06:02:32 +08:00
xinian
ef05dff851 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-13 03:34:37 +08:00
xinian
a0e0822b5a 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-13 03:04:04 +08:00
xinian
dca4d4ffca 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-13 01:39:53 +08:00
xinian
008aa97675 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-12 22:49:29 +08:00
xinian
aa43a2eec9 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-12 22:19:27 +08:00
昔念
7255ef0669 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-12 15:45:54 +08:00
昔念
2f756c77bb 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-12 12:43:28 +08:00
xinian
d0cf598ced refactor: 将物品和货币相关字段从uint32改为int64以支持更大数值范围
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-12 04:28:20 +08:00
xinian
b5feb85792 refactor: 移除PetService中已注释的ModifyBefore方法
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-12 00:57:45 +08:00
xinian
a6af5c8ca6 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-12 00:50:11 +08:00
xinian
22a07ca213 1 2026-02-12 00:49:18 +08:00
xinian
c99923348a perf: 优化空间信息获取时的内存分配
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-12 00:34:33 +08:00
xinian
1f5fc2d254 refactor: 移除空间服务的速率限制代码
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-12 00:33:35 +08:00
xinian
64c3b50860 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-12 00:31:41 +08:00
昔念
0f914eb9b8 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-11 11:58:34 +08:00
昔念
7441a9a88f Merge branch 'main' of https://cnb.cool/blzing/blazing
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-11 11:07:38 +08:00
昔念
71a0ae2157 1 2026-02-11 11:06:28 +08:00
xinian
4359743b7b 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-11 01:13:34 +08:00
xinian
eefdc6ef71 1 2026-02-11 01:13:20 +08:00
xinian
1b930b5a19 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-11 01:05:47 +08:00
xinian
519f2c69a5 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-10 23:06:41 +08:00
xinian
cbfaef7fbb 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-10 22:45:10 +08:00
xinian
a5485de510 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-10 22:09:15 +08:00
昔念
ab1445510a 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-10 13:05:45 +08:00
昔念
f514a4fde1 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-10 12:44:34 +08:00
昔念
d17cd28c94 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-10 11:30:41 +08:00
昔念
40bef8e70c 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-10 10:52:35 +08:00
昔念
fbd8e7dc42 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-10 10:49:23 +08:00
昔念
b007f7c15e Merge branch 'main' of https://cnb.cool/blzing/blazing 2026-02-10 10:49:10 +08:00
昔念
c790b68d47 1 2026-02-10 10:49:01 +08:00
昔念
89645c0f4c 1 2026-02-10 10:07:16 +08:00
xinian
e4bb19ff60 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-10 00:30:23 +08:00
昔念
ecb20ef99d 1 2026-02-09 02:31:50 +08:00
昔念
2860bcfa5c 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-09 01:29:33 +08:00
昔念
ffe3ff18bf 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-08 17:57:42 +08:00
昔念
2edd1ba852 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-08 15:18:50 +08:00
昔念
8988e84a01 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-08 12:18:23 +08:00
昔念
2634e6517e 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-08 12:02:09 +08:00
昔念
0fcd948636 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-08 05:05:01 +08:00
昔念
d17f3eccdb 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-08 04:58:58 +08:00
昔念
ba1483241c 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-08 04:32:01 +08:00
昔念
192c26871d 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-08 04:17:25 +08:00
昔念
659ca8692a 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-08 03:49:11 +08:00
昔念
ad77da1e86 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-08 03:48:56 +08:00
昔念
ec56efb2b3 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-08 03:30:15 +08:00
昔念
916fbcb674 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-08 03:18:00 +08:00
昔念
8ae7009811 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-08 03:05:27 +08:00
昔念
2b25ae6b35 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-08 02:11:46 +08:00
昔念
af29b13ba4 1 2026-02-07 23:53:07 +08:00
昔念
97cc5d42a4 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 23:19:21 +08:00
昔念
ecf971fe31 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 23:09:11 +08:00
昔念
ca2d564e6a 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 23:02:10 +08:00
昔念
6c1f52a86c 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 22:55:24 +08:00
昔念
a991013040 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-07 22:54:44 +08:00
昔念
dba116c57b 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 22:15:23 +08:00
昔念
0e432c2975 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-07 22:14:32 +08:00
昔念
907a3cdbfe 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 22:06:37 +08:00
昔念
d83d76ca8e 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 21:51:34 +08:00
昔念
7d7cc0d174 修复塔离开bug
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 20:50:02 +08:00
昔念
9cf6ad8b88 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 20:16:44 +08:00
昔念
1a0a2badd4 `
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 20:09:02 +08:00
昔念
cdfbc45887 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 19:40:51 +08:00
昔念
7b8276a387 1 2026-02-07 18:26:22 +08:00
昔念
460b92c044 1 2026-02-07 18:21:52 +08:00
昔念
6be35dc045 11
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 16:57:04 +08:00
昔念
cc3be4a58b 1
Some checks failed
ci/woodpecker/push/my-first-workflow Pipeline failed
2026-02-07 16:56:43 +08:00
昔念
a7bfaf92df 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 16:47:33 +08:00
昔念
f7d367b7c1 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 16:44:13 +08:00
昔念
7590943e9d 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 16:23:52 +08:00
昔念
2cf886d825 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 16:17:08 +08:00
昔念
ce2c381116 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 14:59:22 +08:00
昔念
ac3fb4d392 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 13:49:53 +08:00
昔念
43b6e73970 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 13:38:59 +08:00
昔念
d312dfc791 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 04:10:01 +08:00
昔念
6316b393af 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 03:27:56 +08:00
昔念
356d50529c 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 03:09:43 +08:00
昔念
acbb30a9b1 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 03:06:33 +08:00
昔念
0c3f56d7bb 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 02:59:38 +08:00
昔念
bc88d58e59 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 02:52:03 +08:00
昔念
44d937b8bc 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 01:36:43 +08:00
昔念
99ef8fafce 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 01:12:46 +08:00
昔念
3947fbce4b 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-07 00:18:14 +08:00
昔念
637a49e274 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-06 00:12:16 +08:00
昔念
a0d4567d3f 1
All checks were successful
ci/woodpecker/push/my-first-workflow Pipeline was successful
2026-02-05 23:44:07 +08:00
1102 changed files with 98905 additions and 371612 deletions

View File

@@ -1,3 +1,16 @@
$:
vscode:
- runner:
cpus: 6
docker:
build: .ide/Dockerfile
services:
- vscode
- docker
stages:
- name: ls
script: ls -al
main:
push:
- runner:
@@ -14,5 +27,4 @@ main:
username: ${GIT_USERNAME}
password: ${GIT_ACCESS_TOKEN}
force: true
#sync_mode: rebase
sync_mode: push

0
.codex Normal file
View File

4
.gitignore vendored
View File

@@ -43,6 +43,8 @@ logic/logic1
logic/logic1
public/logic-linux-amd64
public/login-linux-amd64
.cache/gomod/**
public/login-login-linux-amd64
public/logic_linux-amd64_1
.cache/**
.agents/**

View File

@@ -1,45 +1,135 @@
# 此文件为远程开发环境配置文件
FROM debian:bookworm
# ==========================================
# 1. 基础环境变量
# ==========================================
ENV GO_VERSION=1.25.0
ENV GOPATH=/root/go
ENV PATH=/usr/local/go/bin:${GOPATH}/bin:${PATH}
ENV LC_ALL=zh_CN.UTF-8
ENV LANG=zh_CN.UTF-8
ENV LANGUAGE=zh_CN.UTF-8
ENV XDG_DATA_HOME=/var/lib
ENV XDG_CACHE_HOME=/workspace/.cache
ENV GOCACHE=/workspace/.cache/go-build
ENV GOMODCACHE=/workspace/.cache/gomod
RUN apt update &&\
apt install -y wget rsync unzip openssh-server vim lsof git git-lfs locales locales-all libgit2-1.5 libgit2-dev net-tools jq curl &&\
rm -rf /var/lib/apt/lists/*
# ==========================================
# 2. Codex 配置 (更换时修改这里重新 build)
# ==========================================
ENV CODEX_BASE_URL="https://api.jucode.cn/v1"
# install golang
RUN curl -fsSLO https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz &&\
rm -rf /usr/local/go && tar -C /usr/local -xzf go${GO_VERSION}.linux-amd64.tar.gz &&\
ln -sf /usr/local/go/bin/go /usr/bin/go &&\
ln -sf /usr/local/go/bin/gofmt /usr/bin/gofmt &&\
curl -sSfL https://raw.github.com/golangci/golangci-lint/master/install.sh | sh -s v1.54.2 &&\
rm -rf go${GO_VERSION}.linux-amd64.tar.gz
ENV CODEX_MODEL="gpt-5.4"
# install code-server
RUN curl -fsSL https://code-server.dev/install.sh | sh
RUN code-server --install-extension dbaeumer.vscode-eslint &&\
code-server --install-extension pinage404.git-extension-pack &&\
code-server --install-extension redhat.vscode-yaml &&\
code-server --install-extension esbenp.prettier-vscode &&\
code-server --install-extension golang.go &&\
code-server --install-extension eamodio.gitlens &&\
code-server --install-extension waderyan.gitblame &&\
code-server --install-extension donjayamanne.githistory &&\
code-server --install-extension mhutchie.git-graph &&\
code-server --install-extension ms-azuretools.vscode-docker &&\
code-server --install-extension PKief.material-icon-theme &&\
code-server --install-extension tencent-cloud.coding-copilot &&\
echo done
ENV OPENAI_API_KEY="sk-E0ZZIFNnD0RkhMC9pT2AGMutz9vNy2VLNrgyyobT5voa81pQ"
# install Go Tools
ENV GOPATH /root/go
ENV PATH="${PATH}:${GOPATH}/bin"
# ==========================================
# 3. 安装系统依赖GolangCode-server
# ==========================================
RUN set -ex; \
apt update && \
apt install -y --no-install-recommends \
wget rsync unzip openssh-server vim lsof git git-lfs \
locales libgit2-1.5 libgit2-dev net-tools jq curl ca-certificates sudo gnupg lsb-release xz-utils && \
curl -fsSLO "https://dl.google.com/go/go${GO_VERSION}.linux-amd64.tar.gz" && \
rm -rf /usr/local/go && tar -C /usr/local -xzf "go${GO_VERSION}.linux-amd64.tar.gz" && \
rm -f "go${GO_VERSION}.linux-amd64.tar.gz" && \
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.54.2 && \
curl -fsSL https://code-server.dev/install.sh | sh && \
apt clean && rm -rf /var/lib/apt/lists/*
RUN go install -v golang.org/x/tools/gopls@latest
# ==========================================
# 4. 安装工具链 (国内加速版)
# ==========================================
RUN set -ex; \
go install -v golang.org/x/tools/gopls@latest && \
go install -v github.com/cweill/gotests/gotests@latest && \
go install -v github.com/josharian/impl@latest && \
go install -v github.com/haya14busa/goplay/cmd/goplay@latest && \
go install -v github.com/go-delve/delve/cmd/dlv@latest && \
go install github.com/goreleaser/goreleaser/v2@latest && \
wget -q "https://npmmirror.com/mirrors/node/v22.11.0/node-v22.11.0-linux-x64.tar.xz" -O /tmp/node.tar.xz && \
tar -xJf /tmp/node.tar.xz -C /usr/local --strip-components=1 && \
rm -f /tmp/node.tar.xz && \
npm config set registry https://registry.npmmirror.com/ && \
npm install -g @openai/codex
# install goreleaser
RUN go install github.com/goreleaser/goreleaser/v2@latest
# ==========================================
# 5. 生成 Codex 配置文件 (独立 RUN 彻底规避格式报错)
# ==========================================
RUN mkdir -p /root/.codex
ENV LC_ALL zh_CN.UTF-8
ENV LANG zh_CN.UTF-8
ENV LANGUAGE zh_CN.UTF-8
RUN cat > /root/.codex/config.toml <<EOF
model_provider = "OpenAI"
model = "${CODEX_MODEL}"
model_reasoning_effort = "high"
disable_response_storage = true
[model_providers.OpenAI]
name = "OpenAI"
base_url = "${CODEX_BASE_URL}"
wire_api = "responses"
requires_openai_auth = true
# 自动压缩触发阈值token数
model_auto_compact_token_limit = 100000 # 超过此值自动压缩
# 上下文窗口大小根据模型调整
model_context_window = 128000
# 压缩后保留的最小上下文
model_compact_min_keep_tokens = 20000
# 自动压缩开关默认true
model_auto_compact = true
EOF
RUN cat > /root/.codex/auth.json <<EOF
{
"auth_mode": "apikey",
"OPENAI_API_KEY": "${OPENAI_API_KEY}"
}
EOF
RUN chmod 600 /root/.codex/auth.json && \
echo "export OPENAI_API_KEY=\"${OPENAI_API_KEY}\"" >> /root/.bashrc && \
echo "export CODEX_API_KEY=\"${OPENAI_API_KEY}\"" >> /root/.bashrc
# ==========================================
# 6. 安装 code-server 插件
# ==========================================
RUN set -eux; \
USER_DATA_DIR=/var/lib/code-server; \
EXTENSIONS_DIR="${USER_DATA_DIR}/extensions"; \
mkdir -p "${EXTENSIONS_DIR}" /root/.vscode-server; \
FAILED_EXTENSIONS=""; \
for ext in \
dbaeumer.vscode-eslint \
redhat.vscode-yaml \
esbenp.prettier-vscode \
golang.go \
eamodio.gitlens \
waderyan.gitblame \
donjayamanne.githistory \
mhutchie.git-graph \
tencent-cloud.coding-copilot\
; do \
if ! /usr/bin/code-server --install-extension "${ext}" --user-data-dir "${USER_DATA_DIR}" --extensions-dir "${EXTENSIONS_DIR}"; then \
FAILED_EXTENSIONS="${FAILED_EXTENSIONS} ${ext}"; \
echo "WARN: extension install failed: ${ext}"; \
fi; \
done; \
rm -rf /root/.vscode-server/extensions /root/extensions; \
ln -s "${EXTENSIONS_DIR}" /root/.vscode-server/extensions; \
ln -s "${EXTENSIONS_DIR}" /root/extensions; \
chmod -R a+rwX "${USER_DATA_DIR}"; \
chmod -R a+rX /root/.vscode-server; \
[ -z "${FAILED_EXTENSIONS}" ] && echo "所有插件安装完成 ✅" || echo "以下插件安装失败:${FAILED_EXTENSIONS}"
# ==========================================
# 7. 统一缓存目录 & 环境变量
# ==========================================
RUN mkdir -p /workspace/.cache/go-build /workspace/.cache/gomod /workspace/.cache/goimports && \
chmod -R a+rwx /workspace/.cache && \
printf '%s\n' \
'export XDG_CACHE_HOME=/workspace/.cache' \
'export GOCACHE=/workspace/.cache/go-build' \
'export GOMODCACHE=/workspace/.cache/gomod' \
>> /etc/profile

30
.ide/help.md Normal file
View File

@@ -0,0 +1,30 @@
https://api.jucode.cn/
fastai.fast 使用谷歌邮箱https://linshiguge.com/白嫖
https://zread.ai/tawer-blog/lmarena-2api/1-overview GLM web2 pai
https://crazyrouter.com/console 模型最便宜,看看能不能1:10
https://agentrouter.org/pricing 签到给,有175
充了十块
使用网址https://www.jnm.lol
使用文档https://fcnkhhtxb5iz.feishu.cn/docx/VyhcdKduJoNCK4x4l25ci10JnZf。
24小时自助faka点luckwk点cn
不要发违禁词看到会回消息长期稳定使用
fastai.fast 575560454@qq.com 575560454

2
.vscode/launch.json vendored
View File

@@ -29,7 +29,7 @@
"request": "launch",
"mode": "auto",
"cwd": "${workspaceFolder}",
"args": ["-id=2"],
"args": ["-id=99"],
"program": "${workspaceFolder}/logic"
}

25
.vscode/settings.json vendored
View File

@@ -1,25 +0,0 @@
{
"goBuild.savedBuildConfig": {
"outputName": "",
"outputDir": "./public",
"zcliEnabled": false,
"targetOS": "linux",
"targetArch": "current",
"enableRace": false,
"enableOptimization": true,
"stripSymbols": true,
"cgoEnabled": false,
"buildTags": "",
"customLdflags": "-X main.Version={{.Version}} -X main.BuildTime={{.BuildTime}} -X main.GitCommitID={{.GitCommit}} -X main.GitBranch={{.GitBranch}} -buildid= -extldflags '-static'",
"verboseMode": false,
"printCommands": false,
"keepWorkDir": false,
"forceRebuild": false,
"dryRun": false,
"trimPath": true,
"currentPreset": "production"
},
"go.toolsEnvVars": {},
"goBuild.zcli.enabled": false,
"cSpell.words": ["struc"]
}

View File

@@ -13,7 +13,7 @@ skip_clone: true
steps:
# ========== 1. 替代clone拉取代码核心依赖 ==========
prepare:
image: alpine/git
image: docker.1ms.run/alpine/git
environment:
# WOODPECKER_SSH_KEY:
# from_secret: WOODPECKER_SSH_KEY
@@ -56,9 +56,10 @@ steps:
# - ssh-keyscan -H github.com > /root/.ssh/known_hosts
# - chmod 600 /root/.ssh/known_hosts
# - echo "🔍 ${#CI_REPO_CLONE_SSH_URL}调试: ${CI_REPO_CLONE_SSH_URL}"
- git config --global core.compression 0
- export GIT_CONFIG_URL="https://cnb:$CNB_ACCK@cnb.cool/blzing/blazing"
- echo "🔍 $CNB_ACCK调试 $GIT_CONFIG_URL"
- echo "🔍 $CNB_ACCK调试 $CNB_ACCK"
- git config --global http.sslVerify false
- git clone --depth 1 --progress -v $GIT_CONFIG_URL
# 拉取代码
@@ -69,13 +70,28 @@ steps:
# ========== 4. 编译Logic服务完全参考GitHub Actions编译配置 ==========
build_logic:
image: golang:1.25
image: docker.m.daocloud.io/golang:1.25
depends_on: [prepare]
environment:
CGO_ENABLED: 0
GO111MODULE: on
GOSUMDB: off
commands:
# 2. 清空主源文件关键先删空再写入
- >
echo "" > /etc/apt/sources.list
# 3. 写入阿里云trixie源匹配golang:1.25的系统版本避免版本混跑
- >
echo "deb http://mirrors.aliyun.com/debian/ trixie main contrib non-free non-free-firmware
deb http://mirrors.aliyun.com/debian/ trixie-updates main contrib non-free non-free-firmware
deb http://mirrors.aliyun.com/debian-security/ trixie-security main contrib non-free non-free-firmware" > /etc/apt/sources.list
# 4. 删除sources.list.d下的所有额外源彻底杜绝官方源
- rm -rf /etc/apt/sources.list.d/*
# 5. 强制更新加超时和缓存清理解决卡住问题
- apt-get clean && apt-get update -y -o Acquire::Timeout=30
# 2. 安装正确的 upx Debian 中包名是 upx-ucl不是 upx
- apt-get install -y upx-ucl
- cd blazing
- mkdir -p build
- BIN_NAME="login_${CI_PIPELINE_CREATED}"
@@ -89,6 +105,9 @@ steps:
-ldflags "-s -w -buildid= -extldflags '-static'" \
-o ./build/$BIN_NAME \
./login
# - |
# strip ./build/$BIN_NAME
# upx --best --lzma ./build/$BIN_NAME
- |
if [ ! -f ./build/$BIN_NAME ]; then
echo "❌ 编译失败:产物$BIN_NAME不存在"
@@ -105,6 +124,9 @@ steps:
-ldflags "-s -w -buildid= -extldflags '-static'" \
-o ./build/$BIN_NAME \
./logic
- |
strip ./build/$BIN_NAME
upx --best --lzma ./build/$BIN_NAME
- |
if [ ! -f ./build/$BIN_NAME ]; then
echo "❌ 编译失败:产物$BIN_NAME不存在"
@@ -120,23 +142,23 @@ steps:
# ========== 6. SCP推送产物依赖编译+配置解析 ==========
scp-exe-to-servers: # 与fetch-deploy-config同级缩进2个空格
image: appleboy/drone-scp:1.6.2 # 子元素缩进4个空格
image: docker.1ms.run/appleboy/drone-scp:1.6.2 # 子元素缩进4个空格
settings: # 子元素缩进4个空格
host: &ssh_host 2697v22.mc5173.cn
port: &ssh_port 16493
host: &ssh_host 43.248.3.21
port: &ssh_port 22
username: &ssh_user root
password: &ssh_pass xIy9PQcBF96C
password: &ssh_pass KQv7yzna7BDukK
source:
- blazing/build/**
target: /opt/blazing/
target: /ext/blazing/
strip_components: 1 # 统一缩进6个空格
skip_verify: true # 统一缩进6个空格
timeout: 30s # 统一缩进6个空格
depends_on: # 子元素缩进4个空格
- build_logic # depends_on内的项缩进6个空格
start-login-logic:
image: appleboy/drone-ssh:1.6.2
image: swr.cn-north-4.myhuaweicloud.com/ddn-k8s/ghcr.io/appleboy/drone-ssh:1.7.7
depends_on: [scp-exe-to-servers]
settings: # 子元素缩进4个空格
host: *ssh_host
@@ -145,7 +167,7 @@ steps:
password: *ssh_pass
script:
- |
cd /opt/blazing/build
cd /ext/blazing/build
ls -t login_* 2>/dev/null | head -1
BIN_NAME=$(ls -t login_* 2>/dev/null | head -1)
echo "BIN_NAME: $BIN_NAME"
@@ -179,9 +201,9 @@ steps:
# 移动logic产物到public目录
LOGIC_BIN=$(ls -t logic_* 2>/dev/null | head -1)
if [ -n "$LOGIC_BIN" ]; then
mkdir -p /opt/blazing/build/public
mv $LOGIC_BIN /opt/blazing/build/public/
echo "✅ Logic产物已移动到 /opt/blazing/build/public/ | 文件: $(basename $LOGIC_BIN)"
mkdir -p /ext/blazing/build/public
mv $LOGIC_BIN /ext/blazing/build/public/
echo "✅ Logic产物已移动到 /ext/blazing/build/public/ | 文件: $(basename $LOGIC_BIN)"
else
echo "⚠️ 未找到Logic产物"
fi

93
AGENTS.md Normal file
View File

@@ -0,0 +1,93 @@
# Repository Guidelines
## Project Structure & Module Organization
This repository is split into multiple Go modules:
- `logic/`: main game logic and fight system (`logic/service/fight/...`)
- `login/`: login service
- `common/`: shared utilities, data, RPC helpers, socket code
- `modules/`: domain modules such as `player`, `task`, `space`
- `public/`: runtime data and configs, including `public/config/*.json`
- `docs/`: engineering notes and feature-specific summaries
Keep changes scoped to the owning module. For example, fight effect work belongs under `logic/service/fight/effect/`.
## Build, Test, and Development Commands
- `cd logic && go test ./service/fight/effect`
Validates effect package changes quickly.
- `cd logic && go test ./...`
Runs all tests in the `logic` module.
- `cd common && go test ./...`
Runs shared utility tests.
- `cd logic && go build ./...`
Checks compile health for the logic module.
CI currently builds Go artifacts through GitHub Actions in `.github/workflows/logic_CI.yml`.
## Coding Style & Naming Conventions
Use standard Go formatting and idioms:
- Run `gofmt -w <file>.go` on edited Go files.
- Use tabs as produced by `gofmt`; do not hand-align spacing.
- Keep package names lowercase.
- Follow existing effect naming: `Effect<ID>` structs in files like `effect_123.go` or grouped files such as `400_480_...go`.
- Keep comments short and descriptive, e.g. `// Effect 400: 若和对手属性相同,则技能威力翻倍`.
## Testing Guidelines
The repo uses Gos built-in `testing` package. Existing tests are sparse, so at minimum:
- run package-level tests for the module you changed
- prefer targeted verification first, then broader `go test ./...` when practical
- name tests with Go conventions, e.g. `TestSqrt`, `TestEffect400`
If no automated test exists, document the package-level command you used to validate the change.
## Commit & Pull Request Guidelines
Recent history is inconsistent (`fix: ...`, `编辑文件 ...`, and short placeholder commits). Prefer clear messages:
- `fix: correct Effect599 damage reduction category handling`
- `docs: update effect refactor summary`
For pull requests, include:
- what changed
- affected module(s)
- validation commands run
- linked issue/task if available
## Contributor Notes
Do not overwrite unrelated local changes. This repo often has a dirty worktree. Prefer additive edits, and update `docs/` when continuing long-running refactors such as fight effects.
## Battle System Notes
Most combat work lives under `logic/service/fight/`. Use the existing split before adding code:
- `action/`: battle action types and turn execution helpers
- `input/`: runtime battle state, effect registration, skill parsing
- `info/`: core battle entities such as pets, skills, damage zones, enums
- `effect/`: skill effects and status logic; most day-to-day fight changes land here
- `node/`: shared effect node behavior and default hooks
- `boss/`: boss-only passive/index effects
- `rule/`, `itemover/`, `top/`: rules, item settlement, ranking-related battle logic
When adding a new skill effect:
- prefer `logic/service/fight/effect/`
- follow existing naming such as `Effect400` or grouped files like `400_480_...go`
- register via `input.InitEffect(...)` or existing helper registration paths
- update `effect_info_map.go` if the effect should appear in local effect descriptions
When investigating missing effects, do not rely only on direct `InitEffect(...)` grep results. This repo also uses shared registration files such as:
- `sterStatusEffects.go`
- `effect_power_doblue.go`
- `EffectAttackMiss.go`
- `EffectPhysicalAttackAddStatus.go`
- `EffectDefeatTrigger.go`
- `effect_attr.go`
Recommended validation for fight changes:
- `cd logic && go test ./service/fight/effect`
- `cd logic && go build ./...`
If you continue long-running effect work, update the matching summary in `docs/` so the next pass can resume without re-scanning the whole package.

View File

@@ -8,11 +8,18 @@
## seer-project
项目结构:
go tool pprof -http :8081 "http://125.208.20.223:54612/debug/debug/pprof/profile"
go tool pprof -http :8081 "http://127.0.0.1:9909/debug/pprof/profile"
go tool pprof -http :8081 "http://202.189.15.67:62672/debug/pprof/profile"
go tool pprof -http :8081 "http://8.162.8.203:9909/debug/pprof//profile"
go tool pprof -http :8081 "http://8.162.23.87:9910/debug/pprof//profile"
go tool pprof -http :8081 "http://61.147.247.7:36855/debug/pprof/profile"
go tool pprof -http :8081 "http://61.147.247.7:43892/debug/pprof/profile"
# 采样 60 秒的 CPU 数据然后通过 HTTP 8081 端口可视化
go tool pprof -http :8081 "http://61.147.247.7:43892/debug/pprof/profile?seconds=300"
详情查看 [文档](./docs)
- [战斗](./docs/battle.md)

View File

@@ -0,0 +1,56 @@
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
)
func main() {
const dsn = "user=user_YrK4j7 password=password_jSDm76 host=43.248.3.21 port=5432 dbname=bl sslmode=disable timezone=Asia/Shanghai"
db, err := sql.Open("postgres", dsn)
if err != nil {
log.Fatal(err)
}
defer db.Close()
var (
id int64
cdkCode string
cdkType int64
exchangeRemainCount int64
bindUserID int64
validEndTime sql.NullTime
remark sql.NullString
)
err = db.QueryRow(`
select id, cdk_code, type, exchange_remain_count, bind_user_id, valid_end_time, remark
from config_gift_cdk
where cdk_code = $1
`, "nrTbdXFBhKkaTdDk").Scan(
&id,
&cdkCode,
&cdkType,
&exchangeRemainCount,
&bindUserID,
&validEndTime,
&remark,
)
if err != nil {
log.Fatal(err)
}
fmt.Printf("id=%d\ncdk_code=%s\ntype=%d\nexchange_remain_count=%d\nbind_user_id=%d\nvalid_end_time=%v\nremark=%q\n",
id,
cdkCode,
cdkType,
exchangeRemainCount,
bindUserID,
validEndTime.Time,
remark.String,
)
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gtime"
)
@@ -17,21 +18,45 @@ type Local struct {
func (l *Local) Upload(ctx g.Ctx) (string, error) {
var (
err error
Request = g.RequestFromCtx(ctx)
request = g.RequestFromCtx(ctx)
file *ghttp.UploadFile
)
file := Request.GetUploadFile("file")
// -------------------------- 核心兼容逻辑适配PHP的字段名 --------------------------
// 优先级uploadfilesPHP转转适配器字段 > files通用多文件字段 > file原字段
// 1. 先尝试获取PHP fof/upload插件的 uploadfiles 字段(单文件)
file = request.GetUploadFile("uploadfiles")
if file == nil {
return "", gerror.New("上传文件为空")
// 2. 再尝试获取 files 字段多文件取第一个兼容PHP多文件上传
files := request.GetUploadFiles("files")
if len(files) > 0 {
file = files[0] // 取第一个文件和PHP Arr::get($data, 'data.0.url')逻辑一致
}
}
// 3. 最后兼容原 file 字段
if file == nil {
file = request.GetUploadFile("file")
}
// 所有字段都无文件,返回错误
if file == nil {
return "", gerror.New("上传文件为空未找到file/files/uploadfiles字段")
}
// -------------------------- 原有存储逻辑不变 --------------------------
// 以当前年月日为目录
dir := gtime.Now().Format("Ymd")
fileName, err := file.Save("./public/uploads/"+dir, true)
// 保存路径:./public/uploads/年月日
saveDir := "./public/uploads/" + dir
// 保存文件(自动重命名避免重复)
fileName, err := file.Save(saveDir, true)
if err != nil {
return "", err
return "", gerror.Wrap(err, "保存上传文件失败")
}
return cool.Config.File.Domain + "/public/uploads/" + dir + "/" + fileName, err
// -------------------------- 拼接访问URL原有逻辑不变 --------------------------
accessURL := "http://" + cool.Config.File.Domain + cool.Config.Address + "/uploads/" + dir + "/" + fileName
return accessURL, nil
}
func (l *Local) GetMode() (data interface{}, err error) {

View File

@@ -98,6 +98,11 @@ func (c *Controller) Delete(ctx context.Context, req *DeleteReq) (res *BaseRes,
if err != nil {
return Fail(err.Error()), err
}
t, _ := data.RowsAffected()
if t == 0 {
return Fail("not found"), err
}
c.Service.ModifyAfter(ctx, "Delete", g.RequestFromCtx(ctx).GetMap())
return Ok(data), err
}
@@ -115,6 +120,10 @@ func (c *Controller) Update(ctx context.Context, req *UpdateReq) (res *BaseRes,
if err != nil {
return Fail(err.Error()), err
}
t, _ := data.RowsAffected()
if t == 0 {
return Fail("not found"), err
}
c.Service.ModifyAfter(ctx, "Update", g.RequestFromCtx(ctx).GetMap())
return Ok(data), err
}

View File

@@ -95,3 +95,17 @@ func Fail(message string) *BaseRes {
// }
// return nil, nil
// }
func RedisDo(ctx context.Context, funcstring string, a ...any) {
conn, err := Redis.Conn(ctx)
if err != nil {
panic(err)
}
defer conn.Close(ctx)
_, err = conn.Do(ctx, "publish", funcstring, a)
if err != nil {
panic(err)
}
}

View File

@@ -1,120 +1,121 @@
package coolconfig
import (
"time"
"github.com/gogf/gf/v2/frame/g"
)
// cool config
type sConfig struct {
AutoMigrate bool `json:"auto_migrate,omitempty"` // 是否自动创建表
Eps bool `json:"eps,omitempty"` // 是否开启eps
File *file `json:"file,omitempty"` // 文件上传配置
Name string `json:"name"` // 项目名称
// LoginPort string `json:"port"`
GameOnlineID uint16 `json:"port_bl"` //这个是命令行输入的参数
ServerInfo ServerList
Address string //rpc端口
}
type ServerList struct {
OnlineID uint16 `gorm:"column:online_id;comment:'在线ID';uniqueIndex" json:"online_id"`
//服务器名称Desc
Name string `gorm:"comment:'服务器名称'" json:"name"`
IP string `gorm:"type:string;comment:'服务器IP'" json:"ip"`
Port uint16 `gorm:"comment:'端口号,通常是小整数'" json:"port"`
IsOpen uint8 `gorm:"default:0;not null;comment:'是否开启'" json:"is_open"`
//登录地址
LoginAddr string `gorm:"type:string;comment:'登录地址'" json:"login_addr"`
//账号
Account string `gorm:"type:string;comment:'账号'" json:"account"`
//密码
Password string `gorm:"type:string;comment:'密码'" json:"password"`
CanPort []uint32 `gorm:"type:jsonb;comment:'可连接端口'" json:"can_port"`
//是否测试服
IsVip uint32 `gorm:"default:0;not null;comment:'是否为VIP服务器'" json:"is_vip"`
//isdebug 是否本地服
IsDebug uint8 `gorm:"default:0;comment:'是否为调试模式'" json:"is_debug"`
//服务器异色概率设定ServerList
ShinyRate uint8 `gorm:"default:0;comment:'异色概率'" json:"shiny_rate"`
//服务器天气设定ServerList
WeatherRate uint8 `gorm:"default:0;comment:'天气概率'" json:"weather_rate"`
//服务器属主Desc
Owner uint32 `gorm:"comment:'服务器属主'" json:"owner"`
Desc string `gorm:"comment:'服务器描述'" json:"desc"`
OldScreen string `gorm:"comment:'服务器screen参数'" json:"old_screen"`
//到期时间ServerList
ExpireTime time.Time `gorm:"default:0;comment:'到期时间'" json:"expire_time"`
}
// OSS相关配置
type oss struct {
Endpoint string `json:"endpoint"`
AccessKeyID string `json:"accessKeyID"`
SecretAccessKey string `json:"secretAccessKey"`
UseSSL bool `json:"useSSL"`
BucketName string `json:"bucketName"`
Location string `json:"location"`
}
// 文件上传配置
type file struct {
Mode string `json:"mode"` // 模式 local oss
Domain string `json:"domain"` // 域名 http://
Oss *oss `json:"oss,omitempty"`
}
// NewConfig new config
func newConfig() *sConfig {
var ctx g.Ctx
config := &sConfig{
AutoMigrate: GetCfgWithDefault(ctx, "blazing.autoMigrate", g.NewVar(false)).Bool(),
Name: GetCfgWithDefault(ctx, "server.name", g.NewVar("")).String(),
Eps: GetCfgWithDefault(ctx, "blazing.eps", g.NewVar(false)).Bool(),
// LoginPort: string(GetCfgWithDefault(ctx, "server.port", g.NewVar("8080")).String()),
Address: GetCfgWithDefault(ctx, "server.address", g.NewVar("8080")).String(),
//GamePort: GetCfgWithDefault(ctx, "server.game", g.NewVar("8080")).Uint64s(),
File: &file{
Mode: GetCfgWithDefault(ctx, "blazing.file.mode", g.NewVar("none")).String(),
Domain: GetCfgWithDefault(ctx, "blazing.file.domain", g.NewVar("http://127.0.0.1:8300")).String(),
Oss: &oss{
Endpoint: GetCfgWithDefault(ctx, "blazing.file.oss.endpoint", g.NewVar("127.0.0.1:9000")).String(),
AccessKeyID: GetCfgWithDefault(ctx, "blazing.file.oss.accessKeyID", g.NewVar("")).String(),
SecretAccessKey: GetCfgWithDefault(ctx, "blazing.file.oss.secretAccessKey", g.NewVar("")).String(),
UseSSL: GetCfgWithDefault(ctx, "blazing.file.oss.useSSL", g.NewVar(false)).Bool(),
BucketName: GetCfgWithDefault(ctx, "blazing.file.oss.bucketName", g.NewVar("blazing")).String(),
Location: GetCfgWithDefault(ctx, "blazing.file.oss.location", g.NewVar("us-east-1")).String(),
},
},
}
return config
}
// qiniu 七牛云配置
type qiniu struct {
AccessKey string `json:"ak"`
SecretKey string `json:"sk"`
Bucket string `json:"bucket"`
CDN string `json:"cdn"`
}
// Config config
var Config = newConfig()
// GetCfgWithDefault get config with default value
func GetCfgWithDefault(ctx g.Ctx, key string, defaultValue *g.Var) *g.Var {
value, err := g.Cfg().GetWithEnv(ctx, key)
if err != nil {
return defaultValue
}
if value.IsEmpty() || value.IsNil() {
return defaultValue
}
return value
}
package coolconfig
import (
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
)
// cool config
type sConfig struct {
AutoMigrate bool `json:"auto_migrate,omitempty"` // 是否自动创建表
Eps bool `json:"eps,omitempty"` // 是否开启eps
File *file `json:"file,omitempty"` // 文件上传配置
Name string `json:"name"` // 项目名称
// LoginPort string `json:"port"`
GameOnlineID uint32 `json:"port_bl"` //这个是命令行输入的参数
ServerInfo ServerList
Address string //rpc端口
}
type ServerList struct {
OnlineID uint32 `gorm:"column:online_id;comment:'在线ID';uniqueIndex" json:"online_id"`
//服务器名称Desc
Name string `gorm:"comment:'服务器名称'" json:"name"`
IP string `gorm:"type:string;comment:'服务器IP'" json:"ip"`
Port uint32 `gorm:"comment:'端口号,通常是小整数'" json:"port"`
IsOpen uint8 `gorm:"default:0;not null;comment:'是否开启'" json:"is_open"`
//登录地址
LoginAddr string `gorm:"type:string;comment:'登录地址'" json:"login_addr"`
//账号
Account string `gorm:"type:string;comment:'账号'" json:"account"`
//密码
Password string `gorm:"type:string;comment:'密码'" json:"password"`
CanPort []uint32 `gorm:"type:jsonb;comment:'可连接端口'" json:"can_port"`
//是否测试服
IsVip uint32 `gorm:"default:0;not null;comment:'是否为VIP服务器'" json:"is_vip"`
//isdebug 是否本地服
IsDebug uint8 `gorm:"default:0;comment:'是否为调试模式'" json:"is_debug"`
//服务器属主Desc
Owner uint32 `gorm:"comment:'服务器属主'" json:"owner"`
Desc string `gorm:"comment:'服务器描述'" json:"desc"`
OldScreen string `gorm:"comment:'服务器screen参数'" json:"old_screen"`
//到期时间ServerList
ExpireTime time.Time `gorm:"default:0;comment:'到期时间'" json:"expire_time"`
}
func (s *ServerList) GetID() string {
return gconv.String(100000*s.OnlineID + s.Port)
}
// OSS相关配置
type oss struct {
Endpoint string `json:"endpoint"`
AccessKeyID string `json:"accessKeyID"`
SecretAccessKey string `json:"secretAccessKey"`
UseSSL bool `json:"useSSL"`
BucketName string `json:"bucketName"`
Location string `json:"location"`
}
// 文件上传配置
type file struct {
Mode string `json:"mode"` // 模式 local oss
Domain string `json:"domain"` // 域名 http://
Oss *oss `json:"oss,omitempty"`
}
// NewConfig new config
func newConfig() *sConfig {
var ctx g.Ctx
config := &sConfig{
AutoMigrate: GetCfgWithDefault(ctx, "blazing.autoMigrate", g.NewVar(false)).Bool(),
Name: GetCfgWithDefault(ctx, "server.name", g.NewVar("")).String(),
Eps: GetCfgWithDefault(ctx, "blazing.eps", g.NewVar(false)).Bool(),
// LoginPort: string(GetCfgWithDefault(ctx, "server.port", g.NewVar("8080")).String()),
Address: GetCfgWithDefault(ctx, "server.address", g.NewVar("8080")).String(),
//GamePort: GetCfgWithDefault(ctx, "server.game", g.NewVar("8080")).Uint64s(),
File: &file{
Mode: GetCfgWithDefault(ctx, "blazing.file.mode", g.NewVar("none")).String(),
Domain: GetCfgWithDefault(ctx, "blazing.file.domain", g.NewVar("http://127.0.0.1:8300")).String(),
Oss: &oss{
Endpoint: GetCfgWithDefault(ctx, "blazing.file.oss.endpoint", g.NewVar("127.0.0.1:9000")).String(),
AccessKeyID: GetCfgWithDefault(ctx, "blazing.file.oss.accessKeyID", g.NewVar("")).String(),
SecretAccessKey: GetCfgWithDefault(ctx, "blazing.file.oss.secretAccessKey", g.NewVar("")).String(),
UseSSL: GetCfgWithDefault(ctx, "blazing.file.oss.useSSL", g.NewVar(false)).Bool(),
BucketName: GetCfgWithDefault(ctx, "blazing.file.oss.bucketName", g.NewVar("blazing")).String(),
Location: GetCfgWithDefault(ctx, "blazing.file.oss.location", g.NewVar("us-east-1")).String(),
},
},
}
return config
}
// qiniu 七牛云配置
type qiniu struct {
AccessKey string `json:"ak"`
SecretKey string `json:"sk"`
Bucket string `json:"bucket"`
CDN string `json:"cdn"`
}
// Config config
var Config = newConfig()
// GetCfgWithDefault get config with default value
func GetCfgWithDefault(ctx g.Ctx, key string, defaultValue *g.Var) *g.Var {
value, err := g.Cfg().GetWithEnv(ctx, key)
if err != nil {
return defaultValue
}
if value.IsEmpty() || value.IsNil() {
return defaultValue
}
return value
}

View File

@@ -52,7 +52,7 @@ func RunFunc(ctx g.Ctx, funcstring string) (err error) {
// ClusterRunFunc 集群运行函数,如果是单机模式, 则直接运行函数
func ClusterRunFunc(ctx g.Ctx, funcstring string) (err error) {
if IsRedisMode {
conn, err := g.Redis("cool").Conn(ctx)
conn, err := Redis.Conn(ctx)
if err != nil {
return err
}
@@ -63,41 +63,3 @@ func ClusterRunFunc(ctx g.Ctx, funcstring string) (err error) {
return RunFunc(ctx, funcstring)
}
}
// ListenFunc 监听函数
func ListenFunc(ctx g.Ctx) {
if IsRedisMode {
conn, err := g.Redis("cool").Conn(ctx)
if err != nil {
panic(err)
}
defer conn.Close(ctx)
_, err = conn.Do(ctx, "subscribe", "cool:func")
if err != nil {
panic(err)
}
for {
data, err := conn.Receive(ctx)
if err != nil {
Logger.Error(ctx, err)
time.Sleep(10 * time.Second)
continue
}
if data != nil {
dataMap := data.MapStrStr()
if dataMap["Kind"] == "subscribe" {
continue
}
if dataMap["Channel"] == "cool:func" {
Logger.Debug(ctx, "执行函数", dataMap["Payload"])
err := RunFunc(ctx, dataMap["Payload"])
if err != nil {
Logger.Error(ctx, "执行函数失败", err)
}
}
}
}
} else {
panic(gerror.New("集群模式下, 请使用Redis作为缓存"))
}
}

View File

@@ -22,14 +22,22 @@ var ctx = context.TODO()
type Cmd struct {
Func reflect.Value //方法函数
Req reflect.Type //请求体
// HeaderFieldIndex 是请求结构体中 TomeeHeader 字段的索引路径。
HeaderFieldIndex []int
// UseConn 标记第二个参数是否为 gnet.Conn。
UseConn bool
// 新增预缓存的req创建函数返回结构体指针
NewReqFunc func() interface{}
// NewReqValue 返回请求结构体指针的 reflect.Value避免重复构造类型信息。
NewReqValue func() reflect.Value
//Res reflect.Value //返回体
}
var CmdCache = make(map[uint32]Cmd, 0)
var (
Logger = glog.New()
Cron = cronex.New() //时间轮
Logger = glog.New()
Cron = cronex.New() //时间轮
Connected int64
)
var Filter *sensitive.Manager
var DefaultGenerator = utils.NewGen(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC), uint8(Config.GameOnlineID))
@@ -37,7 +45,7 @@ var DefaultGenerator = utils.NewGen(time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
func init() {
// 创建 IdGeneratorOptions 对象,可在构造函数中输入 WorkerId
Logger.SetFlags(glog.F_TIME_STD | glog.F_FILE_LONG | glog.F_ASYNC) //设置flag
Logger.Print(ctx, "初始化日志")
// for i := 0; i < 600; i++ {
// glog.Debug(context.Background(), i, "初始化雪花算法", DefaultGenerator.Get())
// }

View File

@@ -19,7 +19,7 @@ require (
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/imroc/req/v3 v3.43.3 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/onsi/ginkgo/v2 v2.16.0 // indirect
github.com/orcaman/concurrent-map/v2 v2.0.1 // indirect

View File

@@ -3,6 +3,7 @@ package cool
import (
_ "blazing/contrib/drivers/pgsql"
"blazing/cool/cooldb"
"sync"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
@@ -10,6 +11,11 @@ import (
"gorm.io/gorm"
)
var (
autoMigrateMu sync.Mutex
autoMigrateModels []IModel
)
// 初始化数据库连接供gorm使用
func InitDB(group string) (*gorm.DB, error) {
// var ctx context.Context
@@ -54,9 +60,33 @@ func getDBbyModel(model IModel) *gorm.DB {
// 根据entity结构体创建表
func CreateTable(model IModel) error {
if Config.AutoMigrate {
autoMigrateMu.Lock()
autoMigrateModels = append(autoMigrateModels, model)
autoMigrateMu.Unlock()
return nil
}
// RunAutoMigrate 显式执行已注册模型的建表/迁移。
func RunAutoMigrate() error {
if !Config.AutoMigrate {
return nil
}
autoMigrateMu.Lock()
models := append([]IModel(nil), autoMigrateModels...)
autoMigrateMu.Unlock()
seen := make(map[string]struct{}, len(models))
for _, model := range models {
key := model.GroupName() + ":" + model.TableName()
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
db := getDBbyModel(model)
return db.AutoMigrate(model)
if err := db.AutoMigrate(model); err != nil {
return err
}
}
return nil
}

View File

@@ -1,15 +1,35 @@
package cool
// 存值示例
func AddClient(id uint16, client *ClientHandler) {
func AddClient(id uint32, client *ClientHandler) {
// 普通mapClientmap[id] = client
Clientmap.Store(id, client) // sync.Map存值
}
// 清理指定clientuid=100000*onlineID+port
func DeleteClientOnly(uid uint32) {
Clientmap.Delete(uid)
}
// 清理指定clientonlineID+port
func DeleteClient(id, port uint32) {
Clientmap.Delete(100000*id + port)
}
// 取值示例
func GetClient(id uint16) (*ClientHandler, bool) {
func GetClient(id, port uint32) (*ClientHandler, bool) {
// 普通mapclient, ok := Clientmap[id]
val, ok := Clientmap.Load(id) // sync.Map取值
val, ok := Clientmap.Load(100000*id + port) // sync.Map取值
if !ok {
return nil, false
}
// 类型断言确保value是*ClientHandler
client, ok := val.(*ClientHandler)
return client, ok
}
func GetClientOnly(uid uint32) (*ClientHandler, bool) {
// 普通mapclient, ok := Clientmap[id]
val, ok := Clientmap.Load(uid) // sync.Map取值
if !ok {
return nil, false
}

View File

@@ -2,6 +2,7 @@ package cool
import (
"context"
"database/sql"
"github.com/gogf/gf/v2/container/garray"
"github.com/gogf/gf/v2/database/gdb"
@@ -11,20 +12,21 @@ import (
)
type IService interface {
ServiceAdd(ctx context.Context, req *AddReq) (data interface{}, err error) // 新增
ServiceDelete(ctx context.Context, req *DeleteReq) (data interface{}, err error) // 删除
ServiceUpdate(ctx context.Context, req *UpdateReq) (data interface{}, err error) // 修改
ServiceInfo(ctx context.Context, req *InfoReq) (data interface{}, err error) // 详情
ServiceList(ctx context.Context, req *ListReq) (data interface{}, err error) // 列表
ServicePage(ctx context.Context, req *PageReq) (data interface{}, err error) // 分页
ModifyBefore(ctx context.Context, method string, param g.MapStrAny) (err error) // 新增|删除|修改前的操作
ModifyAfter(ctx context.Context, method string, param g.MapStrAny) (err error) // 新增|删除|修改后的操作
GetModel() IModel // 获取model
ServiceAdd(ctx context.Context, req *AddReq) (data interface{}, err error) // 新增
ServiceDelete(ctx context.Context, req *DeleteReq) (data sql.Result, err error) // 删除
ServiceUpdate(ctx context.Context, req *UpdateReq) (data sql.Result, err error) // 修改
ServiceInfo(ctx context.Context, req *InfoReq) (data interface{}, err error) // 详情
ServiceList(ctx context.Context, req *ListReq) (data interface{}, err error) // 列表
ServicePage(ctx context.Context, req *PageReq) (data interface{}, err error) // 分页
ModifyBefore(ctx context.Context, method string, param g.MapStrAny) (err error) // 新增|删除|修改前的操作
ModifyAfter(ctx context.Context, method string, param g.MapStrAny) (err error) // 新增|删除|修改后的操作
GetModel() IModel // 获取model
}
type Service struct {
Model IModel
ListQueryOp *QueryOp
PageQueryOp *QueryOp
Where func(ctx context.Context) []g.Array // 删除修改定义条件
InsertParam func(ctx context.Context) g.MapStrAny // Add时插入参数
Before func(ctx context.Context) (err error) // CRUD前的操作
InfoIgnoreProperty string // Info时忽略的字段,多个字段用逗号隔开
@@ -35,11 +37,13 @@ type Service struct {
// List/Add接口条件配置
type QueryOp struct {
FieldEQ []string // 字段等于 多个字段选择以及高级搜索都是这个
KeyWordField []string // 模糊搜索匹配的数据库字段,对应普通搜索
AddOrderby g.MapStrStr // 添加排序
Where func(ctx context.Context) []g.Array // 自定义条件
Select string // 查询字段,多个字段用逗号隔开 如: id,name 或 a.id,a.name,b.name AS bname
FieldEQ []string // 字段等于 多个字段选择以及高级搜索都是这个
DataFieldEQ []string // 新增JSONB data->>'xxx' 字段 = ? 多个字段选择以及高级搜索都是这个
KeyWordField []string // 模糊搜索匹配的数据库字段,对应普通搜索
AddOrderby g.MapStrStr // 添加排序
Where func(ctx context.Context) []g.Array // 自定义条件
Select string // 查询字段,多个字段用逗号隔开 如: id,name 或 a.id,a.name,b.name AS bname
Join []*JoinOp // 关联查询
Extend func(ctx g.Ctx, m *gdb.Model) *gdb.Model // 追加其他条件
ModifyResult func(ctx g.Ctx, data interface{}) interface{} // 修改结果
@@ -104,16 +108,31 @@ func (s *Service) ServiceAdd(ctx context.Context, req *AddReq) (data interface{}
}
// ServiceDelete 删除
func (s *Service) ServiceDelete(ctx context.Context, req *DeleteReq) (data interface{}, err error) {
func (s *Service) ServiceDelete(ctx context.Context, req *DeleteReq) (data sql.Result, err error) {
ids := g.RequestFromCtx(ctx).Get("ids").Slice()
m := g.DB(s.Model.GroupName()).Model(s.Model.TableName())
if s.Where != nil {
where := s.Where(ctx)
if len(where) > 0 {
for _, v := range where {
if len(v) == 3 {
if gconv.Bool(v[2]) {
m.Where(v[0], v[1])
}
}
if len(v) == 2 {
m.Where(v[0], v[1])
}
}
}
}
data, err = m.WhereIn("id", ids).Delete()
return
}
// ServiceUpdate 修改
func (s *Service) ServiceUpdate(ctx context.Context, req *UpdateReq) (data interface{}, err error) {
func (s *Service) ServiceUpdate(ctx context.Context, req *UpdateReq) (data sql.Result, err error) {
r := g.RequestFromCtx(ctx)
rmap := r.GetMap()
if rmap["id"] == nil {
@@ -135,7 +154,24 @@ func (s *Service) ServiceUpdate(ctx context.Context, req *UpdateReq) (data inter
}
}
m := DBM(s.Model)
_, err = m.Data(rmap).Where("id", rmap["id"]).Update()
rmap["updateTime"] = nil
if s.Where != nil {
where := s.Where(ctx)
if len(where) > 0 {
for _, v := range where {
if len(v) == 3 {
if gconv.Bool(v[2]) {
m.Where(v[0], v[1])
}
}
if len(v) == 2 {
m.Where(v[0], v[1])
}
}
}
}
//rmap["id"] = nil
data, err = m.Data(rmap).Where("id", rmap["id"]).Update()
return
}
@@ -199,6 +235,18 @@ func (s *Service) ServiceList(ctx context.Context, req *ListReq) (data interface
}
}
}
// 2. JSONB data->>'xxx' 字段查询(你要的 data 类查找)
if len(s.ListQueryOp.DataFieldEQ) > 0 {
for _, field := range s.ListQueryOp.DataFieldEQ {
if val := r.Get(field); val.String() != "" {
// 关键:拼接 data->>'字段名' = ?
// 错误写法m.Where("data->>::TEXT? = ?", field, val)
// 正确写法:
m.Where("(data->>?)::TEXT = ?", field, val)
}
}
}
// 如果KeyWordField不为空 则添加查询条件
if !r.Get("keyWord").IsEmpty() {
if len(s.ListQueryOp.KeyWordField) > 0 {
@@ -206,7 +254,9 @@ func (s *Service) ServiceList(ctx context.Context, req *ListReq) (data interface
for _, field := range s.ListQueryOp.KeyWordField {
// g.DumpWithType(field)
// builder.WhereLike(field, "%"+r.Get("keyWord").String()+"%")
builder = builder.WhereOrLike(field, "%"+r.Get("keyWord").String()+"%")
builder = builder.WhereOrf(field+"::text LIKE ?", "%"+r.Get("keyWord").String()+"%")
//builder = builder.WhereOrLike(field, "%"+r.Get("keyWord").String()+"%")
}
m.Where(builder)
}
@@ -218,6 +268,8 @@ func (s *Service) ServiceList(ctx context.Context, req *ListReq) (data interface
if len(v) == 3 {
if gconv.Bool(v[2]) {
m.Where(v[0], v[1])
} else {
m.WhereNot(gconv.String(v[0]), v[1])
}
}
if len(v) == 2 {
@@ -301,6 +353,18 @@ func (s *Service) ServicePage(ctx context.Context, req *PageReq) (data interface
}
}
}
// 2. JSONB data->>'xxx' 字段查询(你要的 data 类查找)
if len(s.PageQueryOp.DataFieldEQ) > 0 {
for _, field := range s.PageQueryOp.DataFieldEQ {
if val := r.Get(field); val.String() != "" {
// 关键:拼接 data->>'字段名' = ?
// 错误写法m.Where("data->>::TEXT? = ?", field, val)
// 正确写法:
m.Where("(data->>?)::TEXT = ?", field, val)
}
}
}
// 如果KeyWordField不为空 则添加查询条件
if !r.Get("keyWord").IsEmpty() {
if len(s.PageQueryOp.KeyWordField) > 0 {
@@ -321,6 +385,8 @@ func (s *Service) ServicePage(ctx context.Context, req *PageReq) (data interface
if len(v) == 3 {
if gconv.Bool(v[2]) {
m.Where(v[0], v[1])
} else {
m.WhereNot(gconv.String(v[0]), v[1])
}
}
if len(v) == 2 {

View File

@@ -42,15 +42,15 @@ const (
maxMatrixSize = 227 // 矩阵维度覆盖最大属性ID 226
)
// 合法单属性ID集合快速校验
var validSingleElementIDs = map[int]bool{
// 合法单属性ID集合按ID直接索引避免运行时 map 查找
var validSingleElementIDs = [maxMatrixSize]bool{
1: true, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true, 8: true, 9: true, 10: true,
11: true, 12: true, 13: true, 14: true, 15: true, 16: true, 17: true, 18: true, 19: true, 20: true,
221: true, 222: true, 223: true, 224: true, 225: true, 226: true,
}
// 元素名称映射(全属性对应,便于日志输出)
var elementNameMap = map[ElementType]string{
// 元素名称映射(按ID直接索引,便于日志输出)
var elementNameMap = [maxMatrixSize]string{
ElementTypeGrass: "GRASS",
ElementTypeWater: "WATER",
ElementTypeFire: "FIRE",
@@ -198,46 +198,55 @@ type ElementCombination struct {
ID int // 组合唯一ID
}
// 全局预加载资源(程序启动时init初始化,运行时直接使用
// 全局预加载资源(程序启动时初始化,运行时只读
var (
// 元素组合池key=组合IDvalue=组合实例(预加载所有合法组合)
elementCombinationPool = make(map[int]*ElementCombination, 150) // 128双+26单=154预分配足够容量
// 单属性克制矩阵预初始化所有特殊克制关系默认1.0
matrix [maxMatrixSize][maxMatrixSize]float64
validCombinationIDs [maxMatrixSize]bool
elementCombinationPool [maxMatrixSize]ElementCombination
dualElementSecondaryPool [maxMatrixSize]ElementType
matrix [maxMatrixSize][maxMatrixSize]float64
Calculator *ElementCalculator
)
// init 预加载所有资源(程序启动时执行一次,无并发问题)
func init() {
// 1. 初始化单属性克制矩阵
initFullTableMatrix()
initElementCombinationPool()
Calculator = NewElementCalculator()
}
// 2. 预加载所有单属性组合
for id := range validSingleElementIDs {
combo := &ElementCombination{
Primary: ElementType(id),
Secondary: nil,
ID: id,
func initElementCombinationPool() {
for id, valid := range validSingleElementIDs {
if !valid {
continue
}
validCombinationIDs[id] = true
elementCombinationPool[id] = ElementCombination{
Primary: ElementType(id),
ID: id,
}
elementCombinationPool[id] = combo
}
// 3. 预加载所有双属性组合
for dualID, atts := range dualElementMap {
primaryID, secondaryID := atts[0], atts[1]
// 按ID升序排序保证组合一致性
primary, secondary := ElementType(primaryID), ElementType(secondaryID)
if primary > secondary {
primary, secondary = secondary, primary
}
combo := &ElementCombination{
dualElementSecondaryPool[dualID] = secondary
validCombinationIDs[dualID] = true
elementCombinationPool[dualID] = ElementCombination{
Primary: primary,
Secondary: &secondary,
Secondary: &dualElementSecondaryPool[dualID],
ID: dualID,
}
elementCombinationPool[dualID] = combo
}
}
func isValidCombinationID(id int) bool {
return id > 0 && id < maxMatrixSize && validCombinationIDs[id]
}
// IsDual 判断是否为双属性
func (ec *ElementCombination) IsDual() bool {
return ec.Secondary != nil
@@ -245,84 +254,82 @@ func (ec *ElementCombination) IsDual() bool {
// Elements 获取所有属性列表
func (ec *ElementCombination) Elements() []ElementType {
if ec.IsDual() {
return []ElementType{ec.Primary, *ec.Secondary}
if secondary := ec.Secondary; secondary != nil {
return []ElementType{ec.Primary, *secondary}
}
return []ElementType{ec.Primary}
}
// String 友好格式化输出
func (ec *ElementCombination) String() string {
primaryName := elementNameMap[ec.Primary]
if !ec.IsDual() {
return fmt.Sprintf("(%s)", primaryName)
if secondary := ec.Secondary; secondary != nil {
return fmt.Sprintf("(%s, %s)", elementNameMap[ec.Primary], elementNameMap[*secondary])
}
return fmt.Sprintf("(%s, %s)", primaryName, elementNameMap[*ec.Secondary])
return fmt.Sprintf("(%s)", elementNameMap[ec.Primary])
}
// ElementCalculator 无锁元素克制计算器(依赖预加载资源
// ElementCalculator 无锁元素克制计算器(所有倍数在初始化阶段预计算
type ElementCalculator struct {
offensiveCache map[string]float64 // 攻击克制缓存(运行时填充,无并发写)
offensiveTable [maxMatrixSize][maxMatrixSize]float64
}
// NewElementCalculator 创建计算器实例(仅初始化缓存)
// NewElementCalculator 创建计算器实例(构建只读查表缓存)
func NewElementCalculator() *ElementCalculator {
return &ElementCalculator{
offensiveCache: make(map[string]float64, 4096), // 预分配大容量缓存
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] // 矩阵默认已初始化1.0,特殊值直接返回
return matrix[attacker][defender]
}
// GetCombination 获取元素组合(直接从预加载池读取
// GetCombination 获取元素组合(直接按ID索引
func (c *ElementCalculator) GetCombination(id int) (*ElementCombination, error) {
combo, exists := elementCombinationPool[id]
if !exists {
if !isValidCombinationID(id) {
return nil, fmt.Errorf("invalid element combination ID: %d", id)
}
return combo, nil
return &elementCombinationPool[id], nil
}
// GetOffensiveMultiplier 计算攻击方→防御方的克制倍数(缓存优先
// GetOffensiveMultiplier 计算攻击方→防御方的克制倍数(只读查表
func (c *ElementCalculator) GetOffensiveMultiplier(attackerID, defenderID int) (float64, error) {
// 1. 获取预加载的组合实例
attacker, err := c.GetCombination(attackerID)
if err != nil {
return 0, fmt.Errorf("attacker invalid: %w", err)
if !isValidCombinationID(attackerID) {
return 0, fmt.Errorf("attacker invalid: invalid element combination ID: %d", attackerID)
}
defender, err := c.GetCombination(defenderID)
if err != nil {
return 0, fmt.Errorf("defender invalid: %w", err)
if !isValidCombinationID(defenderID) {
return 0, fmt.Errorf("defender invalid: invalid element combination ID: %d", defenderID)
}
// 2. 缓存键(全局唯一)
cacheKey := fmt.Sprintf("a%d_d%d", attackerID, defenderID)
if val, exists := c.offensiveCache[cacheKey]; exists {
return val, nil
}
// 3. 核心计算+缓存
val := c.calculateMultiplier(attacker, defender)
c.offensiveCache[cacheKey] = val
return val, nil
return c.offensiveTable[attackerID][defenderID], nil
}
// calculateMultiplier 核心克制计算逻辑
func (c *ElementCalculator) calculateMultiplier(attacker, defender *ElementCombination) float64 {
// 场景1单→单
if !attacker.IsDual() && !defender.IsDual() {
return c.getMatrixValue(attacker.Primary, defender.Primary)
}
// 场景2单→双
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
@@ -333,12 +340,10 @@ func (c *ElementCalculator) calculateMultiplier(attacker, defender *ElementCombi
}
}
// 场景3双→单
if !defender.IsDual() {
return c.calculateDualToSingle(attacker.Primary, *attacker.Secondary, defender.Primary)
}
// 场景4双→双
x1, x2 := attacker.Primary, *attacker.Secondary
y1, y2 := defender.Primary, *defender.Secondary
coeffY1 := c.calculateDualToSingle(x1, x2, y1)
@@ -350,7 +355,6 @@ func (c *ElementCalculator) calculateMultiplier(attacker, defender *ElementCombi
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
@@ -361,60 +365,49 @@ func (c *ElementCalculator) calculateDualToSingle(attacker1, attacker2, defender
}
}
var Calculator = NewElementCalculator()
// TestAllScenarios 全场景测试(验证预加载和计算逻辑)
func TestAllScenarios() {
// 测试1单→单草→水
m1, _ := Calculator.GetOffensiveMultiplier(1, 2)
fmt.Println("草→水: %.2f预期2.0", m1)
if math.Abs(m1-2.0) > 0.001 {
fmt.Println("测试1失败实际%.2f", m1)
}
// 测试2特殊单→单混沌→虚空
m2, _ := Calculator.GetOffensiveMultiplier(222, 226)
fmt.Println("混沌→虚空: %.2f预期0.0", m2)
if math.Abs(m2-0.0) > 0.001 {
fmt.Println("测试2失败实际%.2f", m2)
}
// 测试3单→双火→冰龙43
m3, _ := Calculator.GetOffensiveMultiplier(3, 43)
fmt.Println("火→冰龙: %.2f预期1.5", m3)
if math.Abs(m3-1.5) > 0.001 {
fmt.Println("测试3失败实际%.2f", m3)
}
// 测试4双→特殊单混沌暗影92→神灵223
m4, _ := Calculator.GetOffensiveMultiplier(92, 223)
fmt.Println("混沌暗影→神灵: %.2f预期1.25", m4)
if math.Abs(m4-1.25) > 0.001 {
fmt.Println("测试4失败实际%.2f", m4)
}
// 测试5双→双虚空邪灵113→混沌远古98
m5, _ := Calculator.GetOffensiveMultiplier(113, 98)
fmt.Println("虚空邪灵→混沌远古: %.2f预期0.875", m5)
if math.Abs(m5-0.875) > 0.001 {
fmt.Println("测试5失败实际%.2f", m5)
}
// 测试6缓存命中
m6, _ := Calculator.GetOffensiveMultiplier(113, 98)
if math.Abs(m6-m5) > 0.001 {
fmt.Println("测试6失败缓存未命中")
}
// 测试7含无效组合电→地面
m7, _ := Calculator.GetOffensiveMultiplier(5, 7)
fmt.Println("电→地面: %.2f预期0.0", m7)
if math.Abs(m7-0.0) > 0.001 {
fmt.Println("测试7失败实际%.2f", m7)
}
// 测试8双属性含无效电战斗→地面
m8, _ := Calculator.GetOffensiveMultiplier(35, 7)
fmt.Println("电战斗→地面: %.2f预期0.25", m8)
if math.Abs(m8-0.25) > 0.001 {

View File

@@ -1,5 +1,7 @@
package data
import "github.com/gogf/gf/v2/util/grand"
// 1. 质量枚举常量(保持不变)
const (
BitmapFilterQualityLow = 1 // LOW应用1次滤镜
@@ -20,6 +22,35 @@ const (
colorMax = 0xFFFFFF // 颜色值最大值0xRRGGBB
)
func GetDef() GlowFilter {
ret := GlowFilter{
// Color: 16777215, // 0xFFFFFF对应JSON的color:16777215
Alpha: 0.1, // 光圈大小,透明度
BlurX: 8, // 局外光圈大小
BlurY: 8, // 局外光圈大小
Strength: 8, // 颜色对比度
Quality: 1, // 背包内光圈大小
Inner: true, // 对应JSON的inner:true
Knockout: false, // 无JSON值默认false
ColorMatrixFilter: [20]float32{1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0}, // 对应JSON的matrix数组
Level: 2, // 对应JSON的level:"1"转uint8
}
ret.Color = RandomRGBToUint32()
return ret
}
// RandomRGBToUint32 生成随机RGB颜色并转为uint32格式0x00RRGGBB最高8位留空
func RandomRGBToUint32() uint32 {
// 生成0-255的随机R/G/B分量
r := uint32(grand.Intn(256))
g := uint32(grand.Intn(256))
b := uint32(grand.Intn(256))
// 位拼接R左移16位G左移8位B不位移组合成uint32
return (r << 16) | (g << 8) | b
}
// 精灵加shinylen字段
// 3. 核心结构体BlurX/BlurY/Strength 改为 uint8
type GlowFilter struct {
@@ -53,6 +84,6 @@ type GlowFilter struct {
// ItemInfo
// 用于表示发放物品的信息
type ItemInfo struct {
ItemId uint32 `json:"itemId" description:"发放物品ID"` // 发放物品ID
ItemCnt uint32 `json:"itemCount" description:"发放物品的数量"` // 发放物品的数量,
ItemId int64 `struc:"uint32"`
ItemCnt int64 `struc:"uint32"`
}

View File

@@ -121,7 +121,7 @@ func (s *cacheStore[T]) Del(ctx context.Context, key string) error {
if err != nil {
return gerror.Wrapf(err, "删除缓存失败,键: %s", key)
}
fmt.Printf("[INFO] 删除缓存 [%s] 键: %s 成功\n", s.prefix, key)
//fmt.Printf("[INFO] 删除缓存 [%s] 键: %s 成功\n", s.prefix, key)
return nil
}

View File

@@ -20,8 +20,8 @@ func newSessionStore() *cacheStore[uint32] {
}
// newUserOnlineStore 创建用户在线状态缓存实例
func newUserOnlineStore() *cacheStore[uint16] {
return &cacheStore[uint16]{
func newUserOnlineStore() *cacheStore[uint32] {
return &cacheStore[uint32]{
manager: cool.CacheManager,
prefix: "blazing:useronline:",
}
@@ -38,7 +38,7 @@ func newEmailCodeStore() *cacheStore[int] {
// sessionManager 会话管理器
type sessionManager struct {
sessionStore *cacheStore[uint32] // 会话缓存
userOnlineStore *cacheStore[uint16] // 用户在线状态缓存
userOnlineStore *cacheStore[uint32] // 用户在线状态缓存
emailCodeStore *cacheStore[int] // 邮件注册码缓存
}
@@ -52,12 +52,12 @@ func newSessionManager() *sessionManager {
}
// SetUserOnline 设置用户在线状态
func (m *sessionManager) SetUserOnline(userID uint32, serverID uint16) error {
func (m *sessionManager) SetUserOnline(userID uint32, serverID uint32) error {
return m.userOnlineStore.Set(gctx.New(), gconv.String(userID), serverID, 0)
}
// GetUserOnline 获取用户在线状态
func (m *sessionManager) GetUserOnline(userID uint32) (uint16, error) {
func (m *sessionManager) GetUserOnline(userID uint32) (uint32, error) {
return m.userOnlineStore.Get(context.Background(), gconv.String(userID))
}

View File

@@ -5,7 +5,7 @@ type EffectArg struct {
SideEffect []struct {
ID int `json:"ID"`
SideEffectArgcount int `json:"SideEffectArgcount"`
SideEffectArg string `json:"SideEffectArg,omitempty"`
SideEffectArg rawFlexibleString `json:"SideEffectArg,omitempty"`
} `json:"SideEffect"`
} `json:"SideEffects"`
}

View File

@@ -5,7 +5,7 @@ import (
_ "blazing/common/data/xmlres/packed"
"encoding/json"
"os"
"fmt"
"github.com/ECUST-XX/xml"
"github.com/gogf/gf/v2/os/gres"
@@ -14,22 +14,36 @@ import (
var path string
func readConfigContent(path string) []byte {
return gres.GetContent(path)
}
func getXml[T any](path string) T {
// 解析XML到结构体
var xmls T
t1 := gres.GetContent(path)
t1 := readConfigContent(path)
xml.Unmarshal(t1, &xmls)
return xmls
}
func getJson[T any](path string) T {
// 解析XML到结构体
// 解析JSON到结构体
var xmls T
t1 := gres.GetContent(path)
json.Unmarshal(t1, &xmls)
t1 := readConfigContent(path)
if len(t1) == 0 {
fmt.Printf("[xmlres] getJson empty content: path=%s\n", path)
return xmls
}
if err := json.Unmarshal(t1, &xmls); err != nil {
head := string(t1)
if len(head) > 300 {
head = head[:300]
}
fmt.Printf("[xmlres] getJson unmarshal failed: path=%s len=%d err=%v head=%q\n", path, len(t1), err, head)
}
return xmls
}
@@ -40,7 +54,7 @@ var (
// EffectArgsConfig EffectArg //arg参数
//TalkConfig TalkRoot //任务配置
// //Monster MonsterRoot //野怪配置
MonsterMap map[int]TMapConfig
//MonsterMap map[int]TMapConfig
//Skill MovesTbl //技能配置
SkillMap map[int]Move
PetMAP map[int]PetInfo //宠物配置
@@ -58,8 +72,6 @@ var (
func Initfile() {
//gres.Dump()
path1, _ := os.Getwd()
path = path1 + "/public/config/"
path = "config/"
MapConfig = getXml[Maps](path + "210.xml")
@@ -78,19 +90,19 @@ func Initfile() {
})
//TalkConfig = getXml[TalkRoot](path + "talk.xml")
MonsterMap = utils.ToMap(getXml[MonsterRoot](path+"地图配置野怪.xml").Maps, func(m TMapConfig) int {
return m.ID
// MonsterMap = utils.ToMap(getXml[MonsterRoot](path+"地图配置野怪.xml").Maps, func(m TMapConfig) int {
// return m.ID
})
// })
ShopMap = utils.ToMap(getXml[ShopRoot](path+"地图配置野怪.xml").Items, func(m ShopItem) int {
return gconv.Int(m.ProductID)
})
Skill := getXml[MovesTbl](path + "227.xml")
skillConfig := getJson[MovesJSON](path + "moves_flash.json")
SkillMap = make(map[int]Move, len(Skill.Moves))
for _, v := range Skill.Moves {
SkillMap = make(map[int]Move, len(skillConfig.MovesTbl.Moves.Move))
for _, v := range skillConfig.MovesTbl.Moves.Move {
v.SideEffectS = ParseSideEffectArgs(v.SideEffect)
v.SideEffectArgS = ParseSideEffectArgs(v.SideEffectArg)
SkillMap[v.ID] = v
@@ -101,7 +113,11 @@ func Initfile() {
})
PetMAP = utils.ToMap[PetInfo, int](getXml[Monsters](path+"226.xml").Monsters, func(m PetInfo) int {
pets := getXml[Monsters](path + "226.xml").Monsters
for i := range pets {
pets[i].YieldingEVValues = parseYieldingEV(pets[i].YieldingEV)
}
PetMAP = utils.ToMap[PetInfo, int](pets, func(m PetInfo) int {
return m.ID
})

View File

@@ -11,23 +11,24 @@ type Items struct {
}
type Item struct {
ID int `xml:"ID,attr"` // 物品ID与items.xml一致
Name string `xml:"Name,attr"` // 物品名称
Rarity int `xml:"Rarity,attr,omitempty"` // 稀有度
ItemType int `xml:"ItemType,attr"` // 物品类型0胶囊 1体力药剂 2活力药剂
Max int `xml:"Max,attr"` // 最大堆叠数量
Price int `xml:"Price,attr"` // 价格
Bonus float64 `xml:"Bonus,attr,omitempty"` // 倍率(如捕捉胶囊的加成倍数,修正为浮点型)
Tradability int `xml:"Tradability,attr"` // 可交易性0/1
VipTradability int `xml:"VipTradability,attr"` // VIP可交易性0/1
DailyKey int `xml:"DailyKey,attr,omitempty"` // 每日限制键值
DailyOutMax int `xml:"DailyOutMax,attr,omitempty"` // 每日最大产出
Wd int `xml:"wd,attr"` // 未知属性
UseMax int `xml:"UseMax,attr"` // 最大使用次数
LifeTime int `xml:"LifeTime,attr"` // 生命周期0为永久
Purpose int `xml:"purpose,attr"` // 用途标识
Bean int `xml:"Bean,attr,omitempty"` // 豆子数量
Hide int `xml:"Hide,attr"` // 是否隐藏0/1
ID int `xml:"ID,attr"` // 物品ID与items.xml一致
Name string `xml:"Name,attr"` // 物品名称
Rarity int `xml:"Rarity,attr,omitempty"` // 稀有度
ItemType int `xml:"ItemType,attr"` // 物品类型0胶囊 1体力药剂 2活力药剂
Max int `xml:"Max,attr"` // 最大堆叠数量
Price int `xml:"Price,attr"` // 价格
Bonus float64 `xml:"Bonus,attr,omitempty"` // 倍率(如捕捉胶囊的加成倍数,修正为浮点型)
Tradability int `xml:"Tradability,attr"` // 可交易性0/1
VipTradability int `xml:"VipTradability,attr"` // VIP可交易性0/1
DailyKey int `xml:"DailyKey,attr,omitempty"` // 每日限制键值
DailyOutMax int `xml:"DailyOutMax,attr,omitempty"` // 每日最大产出
Wd int `xml:"wd,attr"` // 未知属性
UseMax int `xml:"UseMax,attr"` // 最大使用次数
LifeTime int `xml:"LifeTime,attr"` // 生命周期0为永久
Purpose int `xml:"purpose,attr"` // 用途标识
Bean int `xml:"Bean,attr,omitempty"` // 豆子数量
Hide int `xml:"Hide,attr"` // 是否隐藏0/1
Texture int `xml:"Texture,attr,omitempty"`
Sort int `xml:"Sort,attr,omitempty"` // 排序序号
Des string `xml:"des,attr,omitempty"` // 物品用途XML中无该属性保留字段供自定义
Color string `xml:"color,attr,omitempty"` // 装备名字颜色

View File

@@ -0,0 +1,26 @@
package xmlres
import (
"encoding/json"
"testing"
)
func TestMoveUnmarshalJSONAcceptsNumericName(t *testing.T) {
var move Move
if err := json.Unmarshal([]byte(`{"ID":10001,"Name":1,"Category":1,"Type":8,"Power":35,"MaxPP":35,"Accuracy":95}`), &move); err != nil {
t.Fatalf("unmarshal move failed: %v", err)
}
if move.Name != "1" {
t.Fatalf("expected numeric name to convert to string, got %q", move.Name)
}
}
func TestEffectArgUnmarshalJSONAcceptsNumericSideEffectArg(t *testing.T) {
var cfg EffectArg
if err := json.Unmarshal([]byte(`{"SideEffects":{"SideEffect":[{"ID":1,"SideEffectArgcount":1,"SideEffectArg":3}]}}`), &cfg); err != nil {
t.Fatalf("unmarshal effect arg failed: %v", err)
}
if got := string(cfg.SideEffects.SideEffect[0].SideEffectArg); got != "3" {
t.Fatalf("expected numeric side effect arg to convert to string, got %q", got)
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,11 @@
package xmlres
import "github.com/ECUST-XX/xml"
import (
"strconv"
"strings"
"github.com/ECUST-XX/xml"
)
// Move 表示怪物可学习的技能
type PetMoves struct {
@@ -15,28 +20,28 @@ type LearnableMoves struct {
// PetInfo 表示一个怪物的信息
type PetInfo struct {
ID int `xml:"ID,attr"`
DefName string `xml:"DefName,attr"` // 名字
Type int `xml:"Type,attr"` // 类型
IsLarge int `xml:"IsLarge,attr"` // 是否为大型怪物
GrowthType int `xml:"GrowthType,attr"` // 成长类型
HP int `xml:"HP,attr"` // 血量种族值
Atk uint32 `xml:"Atk,attr"` // 攻击种族值
Def uint32 `xml:"Def,attr"` // 防御种族值
SpAtk uint32 `xml:"SpAtk,attr"` // 特殊攻击种族值
SpDef uint32 `xml:"SpDef,attr"` // 特殊防御种族值
Spd uint32 `xml:"Spd,attr"` // 速度种族值
YieldingExp int `xml:"YieldingExp,attr"` // 击败后获得的经验值
CatchRate int `xml:"CatchRate,attr"` // 捕捉率
YieldingEV string `xml:"YieldingEV,attr"` // 努力值奖励,格式为"HP Atk Def SpAtk SpDef Spd"
EvolvesFrom int `xml:"EvolvesFrom,attr"` // 进化前的怪物ID
EvolvesTo uint32 `xml:"EvolvesTo,attr"` // 进化后的怪物ID
EvolvFlag int `xml:"EvolvFlag,attr"` //<!-- EvolvFlag: 0 - 直接进化(等级到了就进化); 1~49 - 触发进化,默认值: 0 (默认直接进化) -->
EvolvingLv int `xml:"EvolvingLv,attr"` // 进化等级
FreeForbidden int `xml:"FreeForbidden,attr"` // 是否禁止放生
FuseMaster int `xml:"FuseMaster,attr"` // 是否可作为融合主素材
FuseSub int `xml:"FuseSub,attr"` // 是否可作为融合副素材
Gender int `xml:"Gender,attr"` // 性别 0-无性别 1-雄性 2-雌性
ID int `xml:"ID,attr"`
DefName string `xml:"DefName,attr"` // 名字
Type int `xml:"Type,attr"` // 类型
IsLarge int `xml:"IsLarge,attr"` // 是否为大型怪物
GrowthType int `xml:"GrowthType,attr"` // 成长类型
HP int `xml:"HP,attr"` // 血量种族值
Atk uint32 `xml:"Atk,attr"` // 攻击种族值
Def uint32 `xml:"Def,attr"` // 防御种族值
SpAtk uint32 `xml:"SpAtk,attr"` // 特殊攻击种族值
SpDef uint32 `xml:"SpDef,attr"` // 特殊防御种族值
Spd uint32 `xml:"Spd,attr"` // 速度种族值
YieldingExp int `xml:"YieldingExp,attr"` // 击败后获得的经验值
CatchRate int `xml:"CatchRate,attr"` // 捕捉率
YieldingEV string `xml:"YieldingEV,attr"` // 努力值奖励,格式为"HP Atk Def SpAtk SpDef Spd"
EvolvesFrom int `xml:"EvolvesFrom,attr"` // 进化前的怪物ID
EvolvesTo uint32 `xml:"EvolvesTo,attr"` // 进化后的怪物ID
EvolvFlag int `xml:"EvolvFlag,attr"` //<!-- EvolvFlag: 0 - 直接进化(等级到了就进化); 1~49 - 触发进化,默认值: 0 (默认直接进化) -->
EvolvingLv int `xml:"EvolvingLv,attr"` // 进化等级
FreeForbidden int `xml:"FreeForbidden,attr"` // 是否禁止放生
FuseMaster int `xml:"FuseMaster,attr"` // 是否可作为融合主素材
FuseSub int `xml:"FuseSub,attr"` // 是否可作为融合副素材
// Gender int `xml:"Gender,attr"` // 性别 0-无性别 1-雄性 2-雌性
PetClass int `xml:"PetClass,attr"` // 宠物类别
FormParam float64 `xml:"FormParam,attr"` // 形态参数
CharacterAttrParam int `xml:"CharacterAttrParam,attr"` // 特性参数
@@ -45,6 +50,7 @@ type PetInfo struct {
Recycle int `xml:"Recycle,attr"` // 是否可回收
LearnableMoves LearnableMoves `xml:"LearnableMoves"` // 可学习的技能
NaturalEnemy string `xml:"NaturalEnemy,attr"` //天敌
YieldingEVValues []int64 `xml:"-"` // 预解析后的努力值奖励
}
func (basic *PetInfo) GetBasic() uint32 {
@@ -61,3 +67,16 @@ type Monsters struct {
XMLName xml.Name `xml:"Monsters"`
Monsters []PetInfo `xml:"Monster"`
}
func parseYieldingEV(raw string) []int64 {
values := make([]int64, 6)
parts := strings.Fields(raw)
for i := 0; i < len(parts) && i < len(values); i++ {
value, err := strconv.ParseInt(parts[i], 10, 64)
if err != nil {
continue
}
values[i] = value
}
return values
}

View File

@@ -1,6 +1,7 @@
package xmlres
import (
"encoding/json"
"encoding/xml"
"fmt"
"io"
@@ -33,51 +34,156 @@ type MovesTbl struct {
Moves []Move `xml:"Moves>Move"`
EFF []SideEffect `xml:"SideEffects>SideEffect"`
}
type MovesJSON struct {
MovesTbl MovesJSONRoot `json:"MovesTbl"`
}
type MovesJSONRoot struct {
Moves struct {
Move []Move `json:"Move"`
} `json:"Moves"`
SideEffects struct {
SideEffect []SideEffect `json:"SideEffect"`
} `json:"SideEffects"`
}
type MovesMap struct {
XMLName xml.Name `xml:"MovesTbl"`
Moves map[int]Move
EFF []SideEffect `xml:"SideEffects>SideEffect"`
}
type rawFlexibleString string
func (s *rawFlexibleString) UnmarshalJSON(data []byte) error {
text := strings.TrimSpace(string(data))
if text == "" || text == "null" {
*s = ""
return nil
}
if len(text) >= 2 && text[0] == '"' && text[len(text)-1] == '"' {
var decoded string
if err := json.Unmarshal(data, &decoded); err != nil {
return err
}
*s = rawFlexibleString(decoded)
return nil
}
*s = rawFlexibleString(text)
return nil
}
// Move 定义单个技能的结构
type Move struct {
ID int `xml:"ID,attr"`
Name string `xml:"Name,attr"`
ID int `xml:"ID,attr" json:"ID"`
Name string `xml:"Name,attr" json:"Name"`
Category int `xml:"Category,attr"` //属性
Type int `xml:"Type,attr"` //类型
Power int `xml:"Power,attr"` //威力
MaxPP int `xml:"MaxPP,attr"` //最大PP
Accuracy int `xml:"Accuracy,attr"` //命中率
CritRate int `xml:"CritRate,attr,omitempty"` //暴击率
Priority int `xml:"Priority,attr,omitempty"` //优先级
MustHit int `xml:"MustHit,attr,omitempty"` //是否必中
SwapElemType int `xml:"SwapElemType,attr,omitempty"` //技能交换属性
CopyElemType int `xml:"CopyElemType,attr,omitempty"` // 技能复制属性
CritAtkFirst int `xml:"CritAtkFirst,attr,omitempty"` // 先出手时必定致命一击
CritAtkSecond int `xml:"CritAtkSecond,attr,omitempty"` //后出手时必定致命一击
CritSelfHalfHp int `xml:"CritSelfHalfHp,attr,omitempty"` //自身体力低于一半时必定致命一击
CritFoeHalfHp int `xml:"CritFoeHalfHp,attr,omitempty"` //对方体力低于一半时必定致命一击
DmgBindLv int `xml:"DmgBindLv,attr,omitempty"` //使对方受到的伤害值等于自身的等级
PwrBindDv int `xml:"PwrBindDv,attr,omitempty"` //威力power取决于自身的潜力个体值
PwrDouble int `xml:"PwrDouble,attr,omitempty"` //攻击时,若对方处于异常状态, 则威力翻倍;
DmgBindHpDv int `xml:"DmgBindHpDv,attr,omitempty"` //使对方受到的伤害值等于自身的体力值
SideEffect string `xml:"SideEffect,attr,omitempty"`
SideEffectArg string `xml:"SideEffectArg,attr,omitempty"`
Category int `xml:"Category,attr" json:"Category"` //属性
Type int `xml:"Type,attr" json:"Type"` //类型
Power int `xml:"Power,attr" json:"Power"` //威力
MaxPP int `xml:"MaxPP,attr" json:"MaxPP"` //最大PP
Accuracy int `xml:"Accuracy,attr" json:"Accuracy"` //命中率
CritRate int `xml:"CritRate,attr,omitempty" json:"CritRate,omitempty"` //暴击率
Priority int `xml:"Priority,attr,omitempty" json:"Priority,omitempty"` //优先级
MustHit int `xml:"MustHit,attr,omitempty" json:"MustHit,omitempty"` //是否必中
SwapElemType int `xml:"SwapElemType,attr,omitempty" json:"SwapElemType,omitempty"` //技能交换属性
CopyElemType int `xml:"CopyElemType,attr,omitempty" json:"CopyElemType,omitempty"` // 技能复制属性
CritAtkFirst int `xml:"CritAtkFirst,attr,omitempty" json:"CritAtkFirst,omitempty"` // 先出手时必定致命一击
CritAtkSecond int `xml:"CritAtkSecond,attr,omitempty" json:"CritAtkSecond,omitempty"` //后出手时必定致命一击
CritSelfHalfHp int `xml:"CritSelfHalfHp,attr,omitempty" json:"CritSelfHalfHp,omitempty"` //自身体力低于一半时必定致命一击
CritFoeHalfHp int `xml:"CritFoeHalfHp,attr,omitempty" json:"CritFoeHalfHp,omitempty"` //对方体力低于一半时必定致命一击
DmgBindLv int `xml:"DmgBindLv,attr,omitempty" json:"DmgBindLv,omitempty"` //使对方受到的伤害值等于自身的等级
PwrBindDv int `xml:"PwrBindDv,attr,omitempty" json:"PwrBindDv,omitempty"` //威力power取决于自身的潜力个体值
PwrDouble int `xml:"PwrDouble,attr,omitempty" json:"PwrDouble,omitempty"` //攻击时,若对方处于异常状态, 则威力翻倍;
DmgBindHpDv int `xml:"DmgBindHpDv,attr,omitempty" json:"DmgBindHpDv,omitempty"` //使对方受到的伤害值等于自身的体力值
SideEffect string `xml:"SideEffect,attr,omitempty" json:"SideEffect,omitempty"`
SideEffectArg string `xml:"SideEffectArg,attr,omitempty" json:"SideEffectArg,omitempty"`
SideEffectS []int
SideEffectArgS []int
AtkNum int `xml:"AtkNum,attr,omitempty"`
Url string `xml:"Url,attr,omitempty"`
AtkNum int `xml:"AtkNum,attr,omitempty" json:"AtkNum,omitempty"`
AtkType int `xml:"AtkType,attr,omitempty" json:"AtkType,omitempty"` // 0:所有人 1:仅己方 2:仅对方 3:仅自己
Url string `xml:"Url,attr,omitempty" json:"Url,omitempty"`
Info string `xml:"info,attr,omitempty"`
Info string `xml:"info,attr,omitempty" json:"info,omitempty"`
CD *int `xml:"CD,attr"`
CD *int `xml:"CD,attr" json:"CD"`
}
func (m *Move) UnmarshalJSON(data []byte) error {
type moveAlias struct {
ID int `json:"ID"`
Name rawFlexibleString `json:"Name"`
Category int `json:"Category"`
Type int `json:"Type"`
Power int `json:"Power"`
MaxPP int `json:"MaxPP"`
Accuracy int `json:"Accuracy"`
CritRate int `json:"CritRate,omitempty"`
Priority int `json:"Priority,omitempty"`
MustHit int `json:"MustHit,omitempty"`
SwapElemType int `json:"SwapElemType,omitempty"`
CopyElemType int `json:"CopyElemType,omitempty"`
CritAtkFirst int `json:"CritAtkFirst,omitempty"`
CritAtkSecond int `json:"CritAtkSecond,omitempty"`
CritSelfHalfHp int `json:"CritSelfHalfHp,omitempty"`
CritFoeHalfHp int `json:"CritFoeHalfHp,omitempty"`
DmgBindLv int `json:"DmgBindLv,omitempty"`
PwrBindDv int `json:"PwrBindDv,omitempty"`
PwrDouble int `json:"PwrDouble,omitempty"`
DmgBindHpDv int `json:"DmgBindHpDv,omitempty"`
SideEffect rawFlexibleString `json:"SideEffect,omitempty"`
SideEffectArg rawFlexibleString `json:"SideEffectArg,omitempty"`
AtkNum int `json:"AtkNum,omitempty"`
AtkType int `json:"AtkType,omitempty"`
Url string `json:"Url,omitempty"`
Info string `json:"info,omitempty"`
CD *int `json:"CD"`
}
var aux moveAlias
if err := json.Unmarshal(data, &aux); err != nil {
return err
}
*m = Move{
ID: aux.ID,
Name: string(aux.Name),
Category: aux.Category,
Type: aux.Type,
Power: aux.Power,
MaxPP: aux.MaxPP,
Accuracy: aux.Accuracy,
CritRate: aux.CritRate,
Priority: aux.Priority,
MustHit: aux.MustHit,
SwapElemType: aux.SwapElemType,
CopyElemType: aux.CopyElemType,
CritAtkFirst: aux.CritAtkFirst,
CritAtkSecond: aux.CritAtkSecond,
CritSelfHalfHp: aux.CritSelfHalfHp,
CritFoeHalfHp: aux.CritFoeHalfHp,
DmgBindLv: aux.DmgBindLv,
PwrBindDv: aux.PwrBindDv,
PwrDouble: aux.PwrDouble,
DmgBindHpDv: aux.DmgBindHpDv,
SideEffect: string(aux.SideEffect),
SideEffectArg: string(aux.SideEffectArg),
AtkNum: aux.AtkNum,
AtkType: aux.AtkType,
Url: aux.Url,
Info: aux.Info,
CD: aux.CD,
}
return nil
}
type SideEffect struct {
ID int `xml:"ID,attr"`
Help string `xml:"help,attr"`
Des string `xml:"des,attr"`
ID int `xml:"ID,attr" json:"ID"`
Help string `xml:"help,attr" json:"help"`
Des string `xml:"des,attr" json:"des"`
}
// ReadHTTPFile 通过HTTP GET请求获取远程文件内容

View File

@@ -1,6 +1,6 @@
module blazing/common
go 1.23.0
go 1.23
require (
github.com/panjf2000/gnet v1.6.7

View File

@@ -33,7 +33,7 @@ func GetServerInfoList(isdebug int32) []ServerInfo {
}
tt.Name = v.Name
tt.Port = v.Port
tt.Port =uint16( v.Port)
ret1 = append(ret1, *tt)
}
@@ -89,7 +89,7 @@ type ServerInfo struct {
// 服务器IP, 16字节UTF-8, 不足16补齐到16
IP string `struc:"[16]byte"` // 定长模式16字节
// 端口
Port uint16
Port uint16
// 好友在线的个数
Friends uint32
}

237
common/rpc/func.go Normal file
View File

@@ -0,0 +1,237 @@
package rpc
import (
"blazing/cool"
"blazing/logic/service/fight/pvp"
"blazing/logic/service/fight/pvpwire"
"fmt"
"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/util/gconv"
)
// ListenFunc 监听函数
// ListenFunc 改造后的 Redis PubSub 监听函数,支持自动重连。
// 注意PubSub 连接只负责订阅和接收,避免在同一连接上并发 PING。
func ListenFunc(ctx g.Ctx) {
if !cool.IsRedisMode {
panic(gerror.New("集群模式下, 请使用Redis作为缓存"))
}
// 定义常量配置
const (
subscribeTopic = "cool:func" // 订阅的主题
retryDelay = 10 * time.Second // 连接失败重试间隔
)
// 外层循环:负责连接断开后的整体重连
for {
// 检查上下文是否已取消,优雅退出
select {
case <-ctx.Done():
cool.Logger.Info(ctx, "ListenFunc 上下文已取消,退出监听")
return
default:
}
// 1. 建立 Redis 连接
conn, err := cool.Redis.Conn(ctx)
if err != nil {
cool.Logger.Error(ctx, "获取 Redis 连接失败", "error", err, "retry_after", retryDelay)
time.Sleep(retryDelay)
continue
}
// 2. 订阅主题
_, err = conn.Do(ctx, "subscribe", subscribeTopic)
if err != nil {
cool.Logger.Error(ctx, "订阅 Redis 主题失败", "topic", subscribeTopic, "error", err)
_ = conn.Close(ctx)
time.Sleep(retryDelay)
continue
}
cool.Logger.Info(ctx, "成功订阅 Redis 主题", "topic", subscribeTopic)
_, err = conn.Do(ctx, "subscribe", "sun:join") //加入队列
if err != nil {
cool.Logger.Error(ctx, "订阅 Redis 主题失败", "topic", "sun:join", "error", err)
_ = conn.Close(ctx)
time.Sleep(retryDelay)
continue
}
cool.Logger.Info(ctx, "成功订阅 Redis 主题", "topic", "sun:join")
// 3. 循环接收消息
connError := false
for !connError {
select {
case <-ctx.Done():
connError = true
continue
default:
}
// 接收消息
data, err := conn.Receive(ctx)
if err != nil {
cool.Logger.Error(ctx, "Redis PubSub Receive 失败", "error", err)
connError = true // 标记连接错误,触发重连
continue
}
// 处理消息(保留原有业务逻辑)
if data != nil {
dataMap, ok := data.Interface().(*gredis.Message)
if !ok {
continue
}
// if dataMap. == "subscribe" {
// continue
// }
if dataMap.Channel == subscribeTopic {
cool.Logger.Debug(ctx, "执行函数", "payload", dataMap.Payload)
err := cool.RunFunc(ctx, dataMap.Payload)
if err != nil {
cool.Logger.Error(ctx, "执行函数失败", "payload", dataMap.Payload, "error", err)
}
}
if dataMap.Channel == "sun:join" {
fightmap.ADD(dataMap.Payload)
//universalClient, _ := g.Redis("cool").Client().(goredis.UniversalClient)
}
}
}
// 4. 清理资源,准备重连
_ = conn.Close(ctx) // 关闭当前连接
cool.Logger.Info(ctx, "Redis 订阅连接异常,准备重连", "retry_after", retryDelay)
time.Sleep(retryDelay)
}
}
// ListenFight 完全对齐 ListenFunc 写法,修复收不到消息问题。
// 注意PubSub 连接只负责订阅和接收,避免在同一连接上并发 PING。
func ListenFight(ctx g.Ctx) {
if !cool.IsRedisMode {
panic(gerror.New("集群模式下, 请使用Redis作为缓存"))
}
// 定义常量配置(对齐 ListenFunc 风格)
const (
retryDelay = 10 * time.Second // 连接失败重试间隔
)
// 提前拼接订阅主题(避免重复拼接,便于日志打印)
serverID := cool.Config.ServerInfo.GetID()
startTopic := "sun:start:" + serverID
sendPackTopic := "sendpack:" + serverID
pvpServerTopic := pvpwire.ServerTopic(gconv.Uint32(serverID))
pvpCoordinatorTopic := pvpwire.CoordinatorTopicPrefix
// 外层循环:负责连接断开后的整体重连
for {
// 检查上下文是否已取消,优雅退出(对齐 ListenFunc
select {
case <-ctx.Done():
cool.Logger.Info(ctx, "ListenFight 上下文已取消,退出监听")
return
default:
}
// 1. 建立 Redis 连接(完全对齐 ListenFunc
conn, err := cool.Redis.Conn(ctx)
if err != nil {
cool.Logger.Error(ctx, "获取 Redis 连接失败", "error", err, "retry_after", retryDelay)
time.Sleep(retryDelay)
continue
}
// 2. 订阅主题(对齐 ListenFunc 的错误处理,替换 panic 为优雅重连)
subscribeTopics := []string{startTopic, pvpServerTopic}
if cool.Config.GameOnlineID == pvp.CoordinatorOnlineID {
subscribeTopics = append(subscribeTopics, pvpCoordinatorTopic)
}
subscribeFailed := false
for _, topic := range subscribeTopics {
_, err = conn.Do(ctx, "subscribe", topic)
if err != nil {
cool.Logger.Error(ctx, "订阅 Redis 主题失败", "topic", topic, "error", err)
_ = conn.Close(ctx)
time.Sleep(retryDelay)
subscribeFailed = true
break
}
cool.Logger.Info(ctx, "成功订阅 Redis 主题", "topic", topic)
}
if subscribeFailed {
continue
}
// // 订阅 sun:sendpack:服务器ID
// _, err = conn.Do(ctx, "subscribe", sendPackTopic)
// if err != nil {
// cool.Logger.Error(ctx, "订阅 Redis 主题失败", "topic", sendPackTopic, "error", err)
// heartbeatCancel() // 关闭心跳协程
// _ = conn.Close(ctx)
// time.Sleep(retryDelay)
// continue
// }
// cool.Logger.Info(ctx, "成功订阅 Redis 主题", "topic", sendPackTopic)
// 打印监听提示(保留原有日志)
fmt.Println("监听战斗", startTopic)
// 3. 循环接收消息(完全对齐 ListenFunc 逻辑)
connError := false
for !connError {
select {
case <-ctx.Done():
connError = true
continue
default:
}
// 接收消息(和 ListenFunc 保持一致的 Receive 用法)
data, err := conn.Receive(ctx)
if err != nil {
cool.Logger.Error(ctx, "Redis PubSub Receive 失败", "error", err)
connError = true // 标记连接错误,触发重连
continue
}
// 处理消息(完全对齐 ListenFunc 的解析逻辑)
if data != nil {
dataMap, ok := data.Interface().(*gredis.Message)
if !ok {
continue
}
// 处理 sun:start:服务器ID 消息
if dataMap.Channel == startTopic {
fmt.Println("战斗开始", dataMap.Payload)
// universalClient, _ := g.Redis("cool").Client().(goredis.UniversalClient)
}
if dataMap.Channel == pvpServerTopic || dataMap.Channel == pvpCoordinatorTopic {
pvp.HandleRedisMessage(dataMap.Channel, dataMap.Payload)
}
// 【可选】处理 sun:sendpack:服务器ID 消息(如果需要)
if dataMap.Channel == sendPackTopic {
fmt.Println("收到战斗包", dataMap.Payload)
}
}
}
// 4. 清理资源,准备重连(完全对齐 ListenFunc
_ = conn.Close(ctx) // 关闭当前连接
cool.Logger.Info(ctx, "Redis 战斗订阅连接异常,准备重连", "retry_after", retryDelay)
time.Sleep(retryDelay)
}
}

163
common/rpc/pvp_match.go Normal file
View File

@@ -0,0 +1,163 @@
package rpc
import (
"blazing/cool"
"blazing/logic/service/fight/pvpwire"
"context"
"encoding/json"
"fmt"
"sync"
"time"
)
const (
pvpMatchQueueTTL = 12 * time.Second
pvpMatchBanPickSecond = 45
)
type PVPMatchJoinPayload struct {
RuntimeServerID uint32 `json:"runtimeServerId"`
UserID uint32 `json:"userId"`
Nick string `json:"nick"`
FightMode uint32 `json:"fightMode"`
Status uint32 `json:"status"`
CatchTimes []uint32 `json:"catchTimes"`
}
type pvpMatchCoordinator struct {
mu sync.Mutex
queues map[uint32][]pvpwire.QueuePlayerSnapshot
lastSeen map[uint32]time.Time
}
var defaultPVPMatchCoordinator = &pvpMatchCoordinator{
queues: make(map[uint32][]pvpwire.QueuePlayerSnapshot),
lastSeen: make(map[uint32]time.Time),
}
func DefaultPVPMatchCoordinator() *pvpMatchCoordinator {
return defaultPVPMatchCoordinator
}
func (m *pvpMatchCoordinator) JoinOrUpdate(payload PVPMatchJoinPayload) error {
if payload.UserID == 0 || payload.RuntimeServerID == 0 || payload.FightMode == 0 {
return fmt.Errorf("invalid pvp match payload: uid=%d server=%d mode=%d", payload.UserID, payload.RuntimeServerID, payload.FightMode)
}
now := time.Now()
player := pvpwire.QueuePlayerSnapshot{
RuntimeServerID: payload.RuntimeServerID,
UserID: payload.UserID,
Nick: payload.Nick,
FightMode: payload.FightMode,
Status: payload.Status,
JoinedAtUnix: now.Unix(),
CatchTimes: append([]uint32(nil), payload.CatchTimes...),
}
var match *pvpwire.MatchFoundPayload
m.mu.Lock()
m.pruneExpiredLocked(now)
m.removeUserLocked(payload.UserID)
m.lastSeen[payload.UserID] = now
queue := m.queues[payload.FightMode]
if len(queue) > 0 {
host := queue[0]
queue = queue[1:]
m.queues[payload.FightMode] = queue
delete(m.lastSeen, host.UserID)
delete(m.lastSeen, payload.UserID)
result := pvpwire.MatchFoundPayload{
SessionID: buildPVPMatchSessionID(host.UserID, payload.UserID),
Stage: pvpwire.StageBanPick,
Host: host,
Guest: player,
BanPickTimeout: pvpMatchBanPickSecond,
}
match = &result
} else {
m.queues[payload.FightMode] = append(queue, player)
}
m.mu.Unlock()
if match == nil {
return nil
}
if err := publishPVPMatchMessage(pvpwire.ServerTopic(match.Host.RuntimeServerID), pvpwire.MessageTypeMatchFound, *match); err != nil {
return err
}
if match.Guest.RuntimeServerID != match.Host.RuntimeServerID {
if err := publishPVPMatchMessage(pvpwire.ServerTopic(match.Guest.RuntimeServerID), pvpwire.MessageTypeMatchFound, *match); err != nil {
return err
}
}
return nil
}
func (m *pvpMatchCoordinator) Cancel(userID uint32) {
if userID == 0 {
return
}
m.mu.Lock()
defer m.mu.Unlock()
delete(m.lastSeen, userID)
m.removeUserLocked(userID)
}
func (m *pvpMatchCoordinator) pruneExpiredLocked(now time.Time) {
for mode, queue := range m.queues {
next := make([]pvpwire.QueuePlayerSnapshot, 0, len(queue))
for _, queued := range queue {
last := m.lastSeen[queued.UserID]
if last.IsZero() || now.Sub(last) > pvpMatchQueueTTL {
delete(m.lastSeen, queued.UserID)
continue
}
next = append(next, queued)
}
m.queues[mode] = next
}
}
func (m *pvpMatchCoordinator) removeUserLocked(userID uint32) {
for mode, queue := range m.queues {
next := make([]pvpwire.QueuePlayerSnapshot, 0, len(queue))
for _, queued := range queue {
if queued.UserID == userID {
continue
}
next = append(next, queued)
}
m.queues[mode] = next
}
}
func publishPVPMatchMessage(topic, msgType string, body any) error {
payload, err := json.Marshal(body)
if err != nil {
return err
}
envelope, err := json.Marshal(pvpwire.Envelope{
Type: msgType,
Body: payload,
})
if err != nil {
return err
}
conn, err := cool.Redis.Conn(context.Background())
if err != nil {
return err
}
defer conn.Close(context.Background())
_, err = conn.Do(context.Background(), "publish", topic, envelope)
return err
}
func buildPVPMatchSessionID(hostUserID, guestUserID uint32) string {
return fmt.Sprintf("xsvr-%d-%d-%d", hostUserID, guestUserID, time.Now().UnixNano())
}

View File

@@ -1,109 +1,166 @@
package rpc
import (
"blazing/common/data/share"
"blazing/cool"
"context"
"fmt"
"log"
config "blazing/modules/config/service"
"github.com/filecoin-project/go-jsonrpc"
"github.com/gogf/gf/v2/util/gconv"
)
// Define the server handler
type ServerHandler struct{}
// 实现踢人
func (*ServerHandler) Kick(_ context.Context, userid uint32) error {
useid1, err := share.ShareManager.GetUserOnline(userid)
if err != nil {
return fmt.Errorf("user not found", err)
}
cl, ok := cool.GetClient(useid1)
if ok {
err := cl.KickPerson(userid) //实现指定服务器踢人
if err != nil {
return fmt.Errorf("踢人失败", err)
}
}
return nil
}
// 注册logic服务器
func (*ServerHandler) RegisterLogic(ctx context.Context, id, port uint16) error {
fmt.Println("注册logic服务器", id, port)
//TODO 待修复滚动更新可能导致的玩家可以同时在旧服务器和新服务器同时在线的bug
revClient, ok := jsonrpc.ExtractReverseClient[cool.ClientHandler](ctx)
if !ok {
return fmt.Errorf("no reverse client")
}
t := config.NewServerService().GetServerID((id))
aa, ok := cool.GetClient(t.Port)
if ok && aa != nil { //如果已经存在且这个端口已经被存过
aa.QuitSelf(0)
}
cool.AddClient(port, &revClient)
//Refurh()
return nil
}
func CServer() *jsonrpc.RPCServer {
// create a new server instance
rpcServer := jsonrpc.NewServer(jsonrpc.WithReverseClient[cool.ClientHandler](""))
rpcServer.Register("", &ServerHandler{})
return rpcServer
}
var closer jsonrpc.ClientCloser
func StartClient(id, port uint16, callback any) *struct {
Kick func(uint32) error
RegisterLogic func(uint16, uint16) error
} {
var rpcaddr = "ws://" + cool.Config.File.Domain + gconv.String(cool.Config.Address) + "/rpc"
//rpcaddr = "127.0.0.1"
closer1, err := jsonrpc.NewMergeClient(context.Background(),
rpcaddr, "", []interface{}{
&RPCClient,
}, nil, jsonrpc.WithClientHandler("", callback),
jsonrpc.WithReconnFun(func() { RPCClient.RegisterLogic(id, port) }),
)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
//if port != 0 { //注册logic
defer RPCClient.RegisterLogic(id, port)
//}
closer = closer1
return &RPCClient
}
// Setup RPCClient with reverse call handler
var RPCClient struct {
Kick func(uint32) error //踢人
RegisterLogic func(uint16, uint16) error
// UserLogin func(int32, int32) error //用户登录事件
// UserLogout func(int32, int32) error //用户登出事件
}
package rpc
import (
"blazing/common/data/share"
"blazing/cool"
"context"
"fmt"
"log"
"time"
config "blazing/modules/config/service"
"github.com/filecoin-project/go-jsonrpc"
"github.com/gogf/gf/v2/util/gconv"
)
// Define the server handler
type ServerHandler struct{}
const kickForwardTimeout = 3 * time.Second
// 实现踢人
func (*ServerHandler) Kick(_ context.Context, userid uint32) error {
useid1, err := share.ShareManager.GetUserOnline(userid)
if err != nil || useid1 == 0 {
// 请求到达时用户已离线,直接视为成功
return nil
}
cl, ok := cool.GetClientOnly(useid1)
if !ok || cl == nil {
// 目标服务器不在线,清理僵尸在线标记并视为成功
_ = share.ShareManager.DeleteUserOnline(userid)
cool.DeleteClientOnly(useid1)
return nil
}
resultCh := make(chan error, 1)
go func() {
resultCh <- cl.KickPerson(userid) // 实现指定服务器踢人
}()
select {
case callErr := <-resultCh:
if callErr == nil {
return nil
}
// 调用失败后兜底:用户若已离线/切服/目标服不在线都算成功
useid2, err2 := share.ShareManager.GetUserOnline(userid)
if err2 != nil || useid2 == 0 || useid2 != useid1 {
return nil
}
if cl2, ok2 := cool.GetClientOnly(useid2); !ok2 || cl2 == nil {
_ = share.ShareManager.DeleteUserOnline(userid)
cool.DeleteClientOnly(useid2)
return nil
}
// 仍在线则返回失败,不按成功处理
return callErr
case <-time.After(kickForwardTimeout):
// 仅防止无限等待;超时不算成功
useid2, err2 := share.ShareManager.GetUserOnline(userid)
if err2 != nil || useid2 == 0 || useid2 != useid1 {
return nil
}
if cl2, ok2 := cool.GetClientOnly(useid2); !ok2 || cl2 == nil {
_ = share.ShareManager.DeleteUserOnline(userid)
cool.DeleteClientOnly(useid2)
return nil
}
return fmt.Errorf("kick timeout, user still online: uid=%d server=%d", userid, useid2)
}
}
// 注册logic服务器
func (*ServerHandler) RegisterLogic(ctx context.Context, id, port uint32) error {
fmt.Println("注册logic服务器", id, port)
//TODO 待修复滚动更新可能导致的玩家可以同时在旧服务器和新服务器同时在线的bug
revClient, ok := jsonrpc.ExtractReverseClient[cool.ClientHandler](ctx)
if !ok {
return fmt.Errorf("no reverse client")
}
t := config.NewServerService().GetServerID((id))
aa, ok := cool.GetClient(t.OnlineID, t.Port)
if ok && aa != nil { //如果已经存在且这个端口已经被存过
aa.QuitSelf(0)
}
cool.AddClient(100000*id+port, &revClient)
//Refurh()
return nil
}
func (*ServerHandler) MatchJoinOrUpdate(_ context.Context, payload PVPMatchJoinPayload) error {
return DefaultPVPMatchCoordinator().JoinOrUpdate(payload)
}
func (*ServerHandler) MatchCancel(_ context.Context, userID uint32) error {
DefaultPVPMatchCoordinator().Cancel(userID)
return nil
}
func CServer() *jsonrpc.RPCServer {
// create a new server instance
rpcServer := jsonrpc.NewServer(jsonrpc.WithReverseClient[cool.ClientHandler](""))
rpcServer.Register("", &ServerHandler{})
return rpcServer
}
var closer jsonrpc.ClientCloser
func StartClient(id, port uint32, callback any) *struct {
Kick func(uint32) error
RegisterLogic func(uint32, uint32) error
MatchJoinOrUpdate func(PVPMatchJoinPayload) error
MatchCancel func(uint32) error
} {
//cool.Config.File.Domain = "127.0.0.1"
var rpcaddr = "ws://" + cool.Config.File.Domain + gconv.String(cool.Config.Address) + "/rpc"
closer1, err := jsonrpc.NewMergeClient(context.Background(),
rpcaddr, "", []interface{}{
&RPCClient,
}, nil, jsonrpc.WithClientHandler("", callback),
jsonrpc.WithReconnFun(func() { RPCClient.RegisterLogic(id, port) }),
)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
//if port != 0 { //注册logic
defer RPCClient.RegisterLogic(id, port)
//}
closer = closer1
return &RPCClient
}
// Setup RPCClient with reverse call handler
var RPCClient struct {
Kick func(uint32) error //踢人
RegisterLogic func(uint32, uint32) error
MatchJoinOrUpdate func(PVPMatchJoinPayload) error
MatchCancel func(uint32) error
// UserLogin func(int32, int32) error //用户登录事件
// UserLogout func(int32, int32) error //用户登出事件
}

76
common/rpc/user.go Normal file
View File

@@ -0,0 +1,76 @@
package rpc
import (
"blazing/common/data/share"
"blazing/cool"
"blazing/logic/service/common"
"blazing/logic/service/fight/info"
"blazing/modules/player/model"
"blazing/modules/player/service"
"context"
"encoding/json"
"time"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
"github.com/liwnn/zset"
csmap "github.com/mhmtszr/concurrent-swiss-map"
)
type RPCfight struct {
fightmap *csmap.CsMap[int, common.FightI]
zs *zset.ZSet[uint32, *model.PVP]
}
func (r *RPCfight) join(pvp info.RPCFightinfo) {
ret := service.NewPVPService(pvp.PlayerID).Get(pvp.PlayerID)
ret.RankInfo.LastMatchTime = gtime.Now()
r.zs.Add(pvp.PlayerID, ret)
if r.zs.Length() > 1 {
u, _ := r.zs.FindNext(func(i *model.PVP) bool { return i.RankInfo.Score >= ret.RankInfo.Score })
diff := u.RankInfo.Score - ret.RankInfo.Score
// 等待越久,允许区间越大
wait := time.Now().Sub(u.RankInfo.LastMatchTime.Time).Seconds()
maxAllow := 100 + int(wait)*10
if diff < maxAllow {
//找到上一个,如果区间分数少于一定,
//直接进行匹配
useid1, _ := share.ShareManager.GetUserOnline(u.PlayerID)
cool.RedisDo(context.TODO(), "sun:start:"+gconv.String(useid1), info.RPCFightStartinfo{
Serverid: int(useid1),
PlayerID: u.PlayerID,
Mode: pvp.Mode,
Status: pvp.Status,
})
}
}
}
func (r *RPCfight) ADD(s string) {
println("收到sun:join", s)
var pvp []info.RPCFightinfo
json.Unmarshal([]byte(s), &pvp)
if pvp[0].Type == 1 {
r.join(pvp[0])
} else { //==0 退出
r.cancel(pvp[0])
}
}
func (r *RPCfight) cancel(pvp info.RPCFightinfo) {
r.zs.Remove(pvp.PlayerID)
}
///定义map,存储用户对战斗容器的映射,便于外部传入时候进行直接操作
var fightmap = RPCfight{
fightmap: csmap.New[int, common.FightI](),
zs: zset.New[uint32, *model.PVP](func(a, b *model.PVP) bool {
return a.Less(b)
}),
}

View File

@@ -1,211 +1,266 @@
package socket
import (
"context"
"fmt"
"log"
"os"
"sync/atomic"
"time"
"blazing/common/socket/codec"
"blazing/cool"
"blazing/logic/service/player"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/panjf2000/gnet/v2"
)
func (s *Server) Boot() error {
// go s.bootws()
err := gnet.Run(s, s.network+"://"+s.addr,
gnet.WithMulticore(true),
gnet.WithTicker(true),
// gnet.WithReusePort(true),
// gnet.WithReuseAddr(true),
gnet.WithSocketRecvBuffer(s.bufferSize))
if err != nil {
panic(err)
}
return nil
}
func (s *Server) Stop() error {
_ = s.eng.Stop(context.Background())
s.workerPool.Release()
return nil
}
func (s *Server) OnClose(c gnet.Conn, err error) (action gnet.Action) {
defer func() {
if err := recover(); err != nil { // 恢复 panicerr 为 panic 错误值
// 1. 打印错误信息
fmt.Println(context.TODO(), "panic 错误:", err)
}
}()
// 识别 RST 导致的连接中断(错误信息含 "connection reset"
// if err != nil && (strings.Contains(err.Error(), "connection reset") || strings.Contains(err.Error(), "reset by peer")) {
// remoteIP := c.RemoteAddr().(*net.TCPAddr).IP.String()
// log.Printf("RST 攻击检测: 来源 %s, 累计攻击次数 %d", remoteIP)
// // 防护逻辑:临时封禁异常 IP可扩展为 IP 黑名单)
// // go s.tempBlockIP(remoteIP, 5*time.Minute)
// }
//fmt.Println(err, c.RemoteAddr().String(), "断开连接")
atomic.AddInt64(&s.connected, -1)
//logging.Infof("conn[%v] disconnected", c.RemoteAddr().String())
v, _ := c.Context().(*player.ClientData)
v.LF.Close()
if v.Player != nil {
v.Player.Save() //保存玩家数据
}
//}
//关闭连接
return
}
func (s *Server) OnTick() (delay time.Duration, action gnet.Action) {
g.Log().Async().Info(context.Background(), gtime.Now().ISO8601(), cool.Config.ServerInfo.OnlineID, "链接数", atomic.LoadInt64(&s.connected))
if s.quit && atomic.LoadInt64(&s.connected) == 0 {
//执行正常退出逻辑
os.Exit(0)
}
return 30 * time.Second, gnet.None
}
func (s *Server) OnBoot(eng gnet.Engine) gnet.Action {
s.eng = eng
return gnet.None
}
func (s *Server) OnOpen(conn gnet.Conn) (out []byte, action gnet.Action) {
if s.network != "tcp" {
return nil, gnet.Close
}
if conn.Context() == nil {
conn.SetContext(player.NewClientData(conn)) //注入data
}
atomic.AddInt64(&s.connected, 1)
return nil, gnet.None
}
func (s *Server) OnTraffic(c gnet.Conn) (action gnet.Action) {
defer func() {
if err := recover(); err != nil { // 恢复 panicerr 为 panic 错误值
// 1. 打印错误信息
cool.Logger.Error(context.TODO(), "panic 错误:", err)
}
}()
ws := c.Context().(*player.ClientData).Wsmsg
if ws.Tcp { //升级失败时候防止缓冲区溢出
return s.handleTCP(c)
}
tt, len1 := ws.ReadBufferBytes(c)
if tt == gnet.Close {
return gnet.Close
}
ok, action := ws.Upgrade(c)
if action != gnet.None { //连接断开
return action
}
if !ok { //升级失败,说明是tcp连接
ws.Tcp = true
return s.handleTCP(c)
}
// fmt.Println(ws.Buf.Bytes())
c.Discard(len1)
messages, err := ws.Decode(c)
if err != nil {
return gnet.Close
}
if messages == nil {
return
}
t := c.Context().(*player.ClientData)
for _, msg := range messages {
t.LF.Producer().Write(msg.Payload)
//t.OnEvent(msg.Payload)
}
return gnet.None
}
func (s *Server) handleTCP(conn gnet.Conn) (action gnet.Action) {
conn.Context().(*player.ClientData).IsCrossDomain.Do(func() { //跨域检测
handle(conn)
})
data, err := s.codec.Decode(conn)
if err != nil {
if err == codec.ErrIncompletePacket {
return
}
return gnet.Close
}
if t, ok := conn.Context().(*player.ClientData); ok {
t.LF.Producer().Write(data)
}
if conn.InboundBuffered() > 0 {
if err := conn.Wake(nil); err != nil { // wake up the connection manually to avoid missing the leftover data
return gnet.Close
}
}
return action
}
// CROSS_DOMAIN 定义跨域策略文件内容
const CROSS_DOMAIN = "<?xml version=\"1.0\"?><!DOCTYPE cross-domain-policy><cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>\x00"
// TEXT 定义跨域请求的文本格式
const TEXT = "<policy-file-request/>\x00"
func handle(c gnet.Conn) {
// 读取数据并检查是否为跨域请求
data, err := c.Peek(len(TEXT))
if err != nil {
log.Printf("Error reading cross-domain request: %v", err)
return
}
if string(data) == TEXT { //判断是否是跨域请求
log.Printf("Received cross-domain request from %s", c.RemoteAddr())
// 处理跨域请求
c.Write([]byte(CROSS_DOMAIN))
c.Discard(len(TEXT))
return
}
//return
}
package socket
import (
"blazing/common/socket/codec"
"blazing/cool"
"blazing/logic/service/player"
"blazing/modules/config/service"
"bytes"
"context"
"encoding/binary"
"errors"
"log"
"os"
"sync/atomic"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
"github.com/panjf2000/gnet/v2"
)
const (
minPacketLen = 17
maxPacketLen = 10 * 1024
)
func (s *Server) Boot(serverid, port uint32) error {
// go s.bootws()
s.serverid = serverid
s.port = port
err := gnet.Run(s, s.network+"://"+s.addr,
gnet.WithMulticore(true),
gnet.WithTicker(true),
// 其他调优配置↓
gnet.WithTCPNoDelay(gnet.TCPNoDelay), // 禁用Nagle算法降低延迟适合小数据包场景
//gnet.WithReusePort(true), // 开启SO_REUSEPORT多核下提升并发
//gnet.WithReadBufferCap(1024*64), // 读缓冲区64KB根据业务调整默认太小
// gnet.WithWriteBufferCap(1024*64), // 写缓冲区64KB
//gnet.WithLockOSThread(true), // 绑定goroutine到OS线程减少上下文切换
)
if err != nil {
panic(err)
}
return nil
}
func (s *Server) Stop() error {
_ = s.eng.Stop(context.Background())
s.workerPool.Release()
return nil
}
func (s *Server) OnClose(c gnet.Conn, err error) (action gnet.Action) {
defer func() {
if err := recover(); err != nil { // 恢复 panicerr 为 panic 错误值
if t, ok := c.Context().(*player.ClientData); ok {
if t.Player != nil {
if t.Player.Info != nil {
cool.Logger.Error(context.TODO(), "OnClose 错误:", cool.Config.ServerInfo.OnlineID, t.Player.Info.UserID, err)
go t.Player.SaveOnDisconnect()
}
}
} else {
cool.Logger.Error(context.TODO(), "OnClose 错误:", cool.Config.ServerInfo.OnlineID, err)
}
}
}()
atomic.AddInt64(&cool.Connected, -1)
v, _ := c.Context().(*player.ClientData)
if v != nil {
v.Close()
if v.Player != nil {
v.Player.Save() //保存玩家数据
}
}
return
}
func (s *Server) OnTick() (delay time.Duration, action gnet.Action) {
g.Log().Async().Info(context.Background(), gtime.Now().ISO8601(), "服务器ID", cool.Config.ServerInfo.OnlineID, "链接数", atomic.LoadInt64(&cool.Connected))
if s.quit && atomic.LoadInt64(&cool.Connected) == 0 {
os.Exit(0)
}
return 30 * time.Second, gnet.None
}
func (s *Server) OnBoot(eng gnet.Engine) gnet.Action {
s.eng = eng
service.NewServerService().SetServerID(s.serverid, s.port)
return gnet.None
}
func (s *Server) OnOpen(conn gnet.Conn) (out []byte, action gnet.Action) {
if s.network != "tcp" {
return nil, gnet.Close
}
if conn.Context() == nil {
conn.SetContext(player.NewClientData(conn))
}
atomic.AddInt64(&cool.Connected, 1)
return nil, gnet.None
}
func (s *Server) OnTraffic(c gnet.Conn) (action gnet.Action) {
defer func() {
if err := recover(); err != nil {
if t, ok := c.Context().(*player.ClientData); ok {
if t.Player != nil && t.Player.Info != nil {
cool.Logger.Error(context.TODO(), "OnTraffic 错误:", cool.Config.ServerInfo.OnlineID, t.Player.Info.UserID, err)
t.Player.Service.Info.Save(*t.Player.Info)
}
}
}
}()
client := c.Context().(*player.ClientData)
if s.discorse && !client.IsCrossDomainChecked() {
handled, ready, action := handle(c)
if action != gnet.None {
return action
}
if handled {
client.MarkCrossDomainChecked()
return gnet.None
}
if !ready {
return gnet.None
}
client.MarkCrossDomainChecked()
}
ws := client.Wsmsg
if ws.Tcp {
return s.handleTCP(c)
}
readAction, inboundLen := ws.ReadBufferBytes(c)
if readAction == gnet.Close {
return gnet.Close
}
state, action := ws.Upgrade(c)
if action != gnet.None {
return action
}
if state == player.UpgradeNeedMoreData {
return gnet.None
}
if state == player.UpgradeUseTCP {
return s.handleTCP(c)
}
if inboundLen > 0 {
if _, err := c.Discard(inboundLen); err != nil {
return gnet.Close
}
ws.ResetInboundMirror()
}
messages, err := ws.Decode(c)
if err != nil {
return gnet.Close
}
if messages == nil {
return
}
for _, msg := range messages {
if !s.onevent(c, msg.Payload) {
return gnet.Close
}
}
return gnet.None
}
func (s *Server) handleTCP(conn gnet.Conn) (action gnet.Action) {
client := conn.Context().(*player.ClientData)
if s.discorse && !client.IsCrossDomainChecked() {
handled, ready, action := handle(conn)
if action != gnet.None {
return action
}
if !ready {
return gnet.None
}
if handled {
client.MarkCrossDomainChecked()
return gnet.None
}
client.MarkCrossDomainChecked()
}
body, err := s.codec.Decode(conn)
if err != nil {
if errors.Is(err, codec.ErrIncompletePacket) {
return gnet.None
}
return gnet.Close
}
if !s.onevent(conn, body) {
return gnet.Close
}
if conn.InboundBuffered() > 0 {
if err := conn.Wake(nil); err != nil {
return gnet.Close
}
}
return action
}
const CROSS_DOMAIN = "<?xml version=\"1.0\"?><!DOCTYPE cross-domain-policy><cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>\x00"
const TEXT = "<policy-file-request/>\x00"
func handle(c gnet.Conn) (handled bool, ready bool, action gnet.Action) {
probeLen := c.InboundBuffered()
if probeLen == 0 {
return false, false, gnet.None
}
if probeLen > len(TEXT) {
probeLen = len(TEXT)
}
data, err := c.Peek(probeLen)
if err != nil {
log.Printf("Error reading cross-domain request: %v", err)
return false, false, gnet.Close
}
if !bytes.Equal(data, []byte(TEXT[:probeLen])) {
return false, true, gnet.None
}
if probeLen < len(TEXT) {
return false, false, gnet.None
}
if _, err := c.Write([]byte(CROSS_DOMAIN)); err != nil {
return false, true, gnet.Close
}
if _, err := c.Discard(len(TEXT)); err != nil {
return false, true, gnet.Close
}
return true, true, gnet.None
}
func (s *Server) onevent(c gnet.Conn, v []byte) bool {
if !isValidPacket(v) {
return false
}
if t, ok := c.Context().(*player.ClientData); ok {
t.PushEvent(v, s.workerPool.Submit)
}
return true
}
func isValidPacket(v []byte) bool {
if len(v) < minPacketLen || len(v) > maxPacketLen {
return false
}
return binary.BigEndian.Uint32(v[0:4]) == uint32(len(v))
}

View File

@@ -13,9 +13,9 @@ type Handler interface {
}
type Server struct {
gnet.BuiltinEventEngine
eng gnet.Engine
addr string
connected int64
eng gnet.Engine
addr string
network string
multicore bool
bufferSize int
@@ -24,7 +24,9 @@ type Server struct {
handler Handler
discorse bool
quit bool
batchRead int
// batchRead int
serverid uint32
port uint32
}
type Option func(*Server)
@@ -36,9 +38,9 @@ func NewServer(options ...Option) *Server {
// handler: handler.NewTomeeHandler(), //请求返回
codec: codec.NewTomeeSocketCodec(), //默认解码器 len+pack
workerPool: goroutine.Default(),
bufferSize: 4096, //默认缓冲区大小
bufferSize: 40960, //默认缓冲区大小
multicore: true,
batchRead: 8,
//batchRead: 8,
//discorse: true,
}
for _, option := range options {

View File

@@ -3,6 +3,7 @@ package socket
import (
"blazing/logic/service/player"
"fmt"
"os"
"time"
)
@@ -12,9 +13,9 @@ type Broadcast struct {
func (s *Server) Broadcast(t string) int {
player.Mainplayer.Range(func(key uint32, value *player.Player) bool {
player.Mainplayer.Range(func(key uint32, value *player.ClientData) bool {
value.SendPackCmd(50003, &Broadcast{
value.Player.SendPackCmd(50003, &Broadcast{
Name: t,
})
return true
@@ -41,30 +42,32 @@ func (s *Server) QuitSelf(a int) error {
s.quit = true
if a != 0 {
player.Mainplayer.Range(func(key uint32, value *player.Player) bool {
player.Mainplayer.Range(func(key uint32, value *player.ClientData) bool {
if value != nil {
value.Kick()
value.Player.Kick(true)
}
return false
})
} else {
go func() {
player.Mainplayer.Range(func(key uint32, value *player.Player) bool {
player.Mainplayer.Range(func(key uint32, value *player.ClientData) bool {
if value != nil {
value.KickMessage()
value.Player.KickMessage()
}
return false
})
<-time.After(10 * time.Minute)
player.Mainplayer.Range(func(key uint32, value *player.Player) bool {
player.Mainplayer.Range(func(key uint32, value *player.ClientData) bool {
if value != nil {
value.Kick()
value.Player.Kick(true)
}
return false
})
os.Exit(0)
}()
}

View File

@@ -3,6 +3,13 @@ package bitset32
import "math/bits"
func popcntSlice(s []uint32) uint64 {
// int r = 0;
// while(n)
// {
// n &amp;= (n - 1);
// ++r;
// }
var cnt int
for _, x := range s {
cnt += bits.OnesCount32(x)

View File

@@ -1,221 +1,195 @@
package csmap
import (
"blazing/cool"
"context"
"encoding/json"
"sync"
"github.com/mhmtszr/concurrent-swiss-map/maphash"
"github.com/mhmtszr/concurrent-swiss-map/swiss"
"sync/atomic"
)
// CsMap 基于官方 sync.Map 重构,完全兼容原有接口
type CsMap[K comparable, V any] struct {
hasher func(key K) uint64
shards []shard[K, V]
shardCount uint64
size uint64
inner sync.Map // 核心替换为官方 sync.Map
}
type HashShardPair[K comparable, V any] struct {
shard shard[K, V]
hash uint64
}
type shard[K comparable, V any] struct {
items *swiss.Map[K, V]
*sync.RWMutex
}
// OptFunc is a type that is used in New function for passing options.
// 以下配置方法保留(兼容原有调用方式,但内部无实际作用)
type OptFunc[K comparable, V any] func(o *CsMap[K, V])
// New function creates *CsMap[K, V].
// New 创建基于 sync.Map 的并发安全 Map兼容原有配置参数参数无实际作用
func New[K comparable, V any](options ...OptFunc[K, V]) *CsMap[K, V] {
m := CsMap[K, V]{
hasher: maphash.NewHasher[K]().Hash,
shardCount: 32,
}
m := &CsMap[K, V]{}
// 遍历配置项(兼容原有代码,无实际逻辑)
for _, option := range options {
option(&m)
option(m)
}
m.shards = make([]shard[K, V], m.shardCount)
for i := 0; i < int(m.shardCount); i++ {
m.shards[i] = shard[K, V]{items: swiss.NewMap[K, V](uint32((m.size / m.shardCount) + 1)), RWMutex: &sync.RWMutex{}}
}
return &m
return m
}
// // Create creates *CsMap.
// //
// // Deprecated: New function should be used instead.
// func Create[K comparable, V any](options ...func(options *CsMap[K, V])) *CsMap[K, V] {
// m := CsMap[K, V]{
// hasher: maphash.NewHasher[K]().Hash,
// shardCount: 32,
// }
// for _, option := range options {
// option(&m)
// }
// m.shards = make([]shard[K, V], m.shardCount)
// for i := 0; i < int(m.shardCount); i++ {
// m.shards[i] = shard[K, V]{items: swiss.NewMap[K, V](uint32((m.size / m.shardCount) + 1)), RWMutex: &sync.RWMutex{}}
// }
// return &m
// }
// 保留原有配置方法(空实现,保证接口兼容)
func WithShardCount[K comparable, V any](count uint64) func(csMap *CsMap[K, V]) {
return func(csMap *CsMap[K, V]) {
csMap.shardCount = count
}
return func(csMap *CsMap[K, V]) {}
}
func WithCustomHasher[K comparable, V any](h func(key K) uint64) func(csMap *CsMap[K, V]) {
return func(csMap *CsMap[K, V]) {
csMap.hasher = h
}
return func(csMap *CsMap[K, V]) {}
}
func WithSize[K comparable, V any](size uint64) func(csMap *CsMap[K, V]) {
return func(csMap *CsMap[K, V]) {
csMap.size = size
}
return func(csMap *CsMap[K, V]) {}
}
func (m *CsMap[K, V]) getShard(key K) HashShardPair[K, V] {
u := m.hasher(key)
return HashShardPair[K, V]{
hash: u,
shard: m.shards[u%m.shardCount],
}
}
// -------------------------- 核心操作方法(基于 sync.Map 实现) --------------------------
// Store 存储键值对,兼容原有接口
func (m *CsMap[K, V]) Store(key K, value V) {
hashShardPair := m.getShard(key)
shard := hashShardPair.shard
shard.Lock()
shard.items.PutWithHash(key, value, hashShardPair.hash)
shard.Unlock()
m.inner.Store(key, value)
}
func (m *CsMap[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
T, OK := m.inner.LoadOrStore(key, value)
return T.(V), OK
}
// Delete 删除指定键,返回是否删除成功
func (m *CsMap[K, V]) Delete(key K) bool {
hashShardPair := m.getShard(key)
shard := hashShardPair.shard
shard.Lock()
defer shard.Unlock()
return shard.items.DeleteWithHash(key, hashShardPair.hash)
// sync.Map.Delete 无返回值,需先 Load 判断是否存在
_, ok := m.inner.Load(key)
if ok {
m.inner.Delete(key)
}
return ok
}
// DeleteIf 满足条件时删除
func (m *CsMap[K, V]) DeleteIf(key K, condition func(value V) bool) bool {
hashShardPair := m.getShard(key)
shard := hashShardPair.shard
shard.Lock()
defer shard.Unlock()
value, ok := shard.items.GetWithHash(key, hashShardPair.hash)
if ok && condition(value) {
return shard.items.DeleteWithHash(key, hashShardPair.hash)
// 先 Load 获取值,再判断条件
val, ok := m.inner.Load(key)
if !ok {
return false
}
v, okCast := val.(V)
if !okCast {
return false
}
if condition(v) {
m.inner.Delete(key)
return true
}
return false
}
// Load 获取指定键的值
func (m *CsMap[K, V]) Load(key K) (V, bool) {
hashShardPair := m.getShard(key)
shard := hashShardPair.shard
shard.RLock()
defer shard.RUnlock()
return shard.items.GetWithHash(key, hashShardPair.hash)
}
func (m *CsMap[K, V]) Has(key K) bool {
hashShardPair := m.getShard(key)
shard := hashShardPair.shard
shard.RLock()
defer shard.RUnlock()
return shard.items.HasWithHash(key, hashShardPair.hash)
}
func (m *CsMap[K, V]) Clear() {
for i := range m.shards {
shard := m.shards[i]
shard.Lock()
shard.items.Clear()
shard.Unlock()
var zero V
val, ok := m.inner.Load(key)
if !ok {
return zero, false
}
// 类型断言(保证类型安全)
v, okCast := val.(V)
if !okCast {
return zero, false
}
return v, true
}
// Has 判断键是否存在
func (m *CsMap[K, V]) Has(key K) bool {
_, ok := m.inner.Load(key)
return ok
}
// Clear 清空所有数据
func (m *CsMap[K, V]) Clear() {
// sync.Map 无直接 Clear 方法,通过 Range 遍历删除
m.inner.Range(func(key, value any) bool {
m.inner.Delete(key)
return true
})
}
// Count 统计元素数量
func (m *CsMap[K, V]) Count() int {
count := 0
for i := range m.shards {
shard := m.shards[i]
shard.RLock()
count += shard.items.Count()
shard.RUnlock()
}
m.inner.Range(func(key, value any) bool {
count++
return true
})
return count
}
// SetIfAbsent 仅当键不存在时设置值
func (m *CsMap[K, V]) SetIfAbsent(key K, value V) {
hashShardPair := m.getShard(key)
shard := hashShardPair.shard
shard.Lock()
_, ok := shard.items.GetWithHash(key, hashShardPair.hash)
if !ok {
shard.items.PutWithHash(key, value, hashShardPair.hash)
}
shard.Unlock()
m.inner.LoadOrStore(key, value)
}
func (m *CsMap[K, V]) SetIf(key K, conditionFn func(previousVale V, previousFound bool) (value V, set bool)) {
hashShardPair := m.getShard(key)
shard := hashShardPair.shard
shard.Lock()
value, found := shard.items.GetWithHash(key, hashShardPair.hash)
value, ok := conditionFn(value, found)
if ok {
shard.items.PutWithHash(key, value, hashShardPair.hash)
// SetIf 根据条件设置值
func (m *CsMap[K, V]) SetIf(key K, conditionFn func(previousValue V, previousFound bool) (value V, set bool)) {
prevVal, found := m.inner.Load(key)
var prevV V
if found {
prevV, _ = prevVal.(V)
}
// 执行条件函数
newVal, set := conditionFn(prevV, found)
if set {
m.inner.Store(key, newVal)
}
shard.Unlock()
}
// SetIfPresent 仅当键存在时设置值
func (m *CsMap[K, V]) SetIfPresent(key K, value V) {
hashShardPair := m.getShard(key)
shard := hashShardPair.shard
shard.Lock()
_, ok := shard.items.GetWithHash(key, hashShardPair.hash)
if ok {
shard.items.PutWithHash(key, value, hashShardPair.hash)
// 先判断是否存在,再设置
if _, ok := m.inner.Load(key); ok {
m.inner.Store(key, value)
}
shard.Unlock()
}
// IsEmpty 判断是否为空
func (m *CsMap[K, V]) IsEmpty() bool {
return m.Count() == 0
}
// Tuple 保留原有结构体(兼容序列化逻辑)
type Tuple[K comparable, V any] struct {
Key K
Val V
}
// Range If the callback function returns true iteration will stop.
// -------------------------- 关键修复Range 方法(无锁阻塞风险) --------------------------
func (m *CsMap[K, V]) Range(f func(key K, value V) (stop bool)) {
ch := make(chan Tuple[K, V], m.Count())
if f == nil {
return
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var stopFlag atomic.Bool
listenCompleted := m.listen(f, ch)
m.produce(ctx, ch)
listenCompleted.Wait()
// 基于 sync.Map 的 Range 实现,无额外 goroutine/channel
m.inner.Range(func(key, value any) bool {
// 检测终止标志
if stopFlag.Load() {
return false
}
// 类型断言
k, okK := key.(K)
v, okV := value.(V)
if !okK || !okV {
return true // 类型不匹配时跳过,继续遍历
}
// 执行用户回调
if f(k, v) {
stopFlag.Store(true)
return false // 终止遍历
}
return true
})
}
// -------------------------- 序列化方法(兼容原有逻辑) --------------------------
func (m *CsMap[K, V]) MarshalJSON() ([]byte, error) {
tmp := make(map[K]V, m.Count())
m.Range(func(key K, value V) (stop bool) {
@@ -226,71 +200,18 @@ func (m *CsMap[K, V]) MarshalJSON() ([]byte, error) {
}
func (m *CsMap[K, V]) UnmarshalJSON(b []byte) error {
tmp := make(map[K]V, m.Count())
tmp := make(map[K]V)
if err := json.Unmarshal(b, &tmp); err != nil {
return err
}
// 清空原有数据
m.Clear()
// 批量存储
for key, val := range tmp {
m.Store(key, val)
}
return nil
}
func (m *CsMap[K, V]) produce(ctx context.Context, ch chan Tuple[K, V]) {
var wg sync.WaitGroup
wg.Add(len(m.shards))
for i := range m.shards {
go func(i int) {
defer wg.Done()
defer func() {
if err := recover(); err != nil { // 恢复 panicerr 为 panic 错误值
// 1. 打印错误信息
cool.Logger.Error(context.TODO(), "csmap panic 错误:", err)
}
}()
shard := m.shards[i]
shard.RLock()
shard.items.Iter(func(k K, v V) (stop bool) {
select {
case <-ctx.Done():
return true
default:
ch <- Tuple[K, V]{Key: k, Val: v}
}
return false
})
shard.RUnlock()
}(i)
}
go func() {
wg.Wait()
close(ch)
}()
}
func (m *CsMap[K, V]) listen(f func(key K, value V) (stop bool), ch chan Tuple[K, V]) *sync.WaitGroup {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
defer func() {
if err := recover(); err != nil { // 恢复 panicerr 为 panic 错误值
// 1. 打印错误信息
cool.Logger.Error(context.TODO(), " csmap panic 错误:", err)
}
}()
for t := range ch {
if stop := f(t.Key, t.Val); stop {
return
}
}
}()
return &wg
}
// -------------------------- 移除所有无用的旧方法produce/listen 等) --------------------------

View File

@@ -2,33 +2,4 @@ module github.com/zmexing/go-sensitive-word
go 1.20
require (
github.com/imroc/req/v3 v3.42.3
github.com/orcaman/concurrent-map/v2 v2.0.1
)
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/onsi/ginkgo/v2 v2.16.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
github.com/quic-go/quic-go v0.40.1 // indirect
github.com/refraction-networking/utls v1.6.3 // indirect
github.com/stretchr/testify v1.11.1 // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.33.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/protobuf v1.31.0 // indirect
)
require github.com/orcaman/concurrent-map/v2 v2.0.1

View File

@@ -1,55 +1,2 @@
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/imroc/req/v3 v3.42.3 h1:ryPG2AiwouutAopwPxKpWKyxgvO8fB3hts4JXlh3PaE=
github.com/imroc/req/v3 v3.42.3/go.mod h1:Axz9Y/a2b++w5/Jht3IhQsdBzrG1ftJd1OJhu21bB2Q=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM=
github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c=
github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q=
github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
github.com/refraction-networking/utls v1.6.3 h1:MFOfRN35sSx6K5AZNIoESsBuBxS2LCgRilRIdHb6fDc=
github.com/refraction-networking/utls v1.6.3/go.mod h1:yil9+7qSl+gBwJqztoQseO6Pr3h62pQoY1lXiNR/FPs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -2,13 +2,15 @@ package store
import (
"bufio"
"errors"
"github.com/imroc/req/v3"
cmap "github.com/orcaman/concurrent-map/v2"
"fmt"
"time"
"io"
"net/http"
"os"
"strings"
cmap "github.com/orcaman/concurrent-map/v2"
)
// MemoryModel 使用并发 map 实现的内存词库
@@ -62,26 +64,40 @@ func (m *MemoryModel) LoadDictEmbed(contents ...string) error {
}
// 从远程 HTTP 地址加载词库
// LoadDictHttp 批量从 HTTP 地址加载字典(标准库 net/http 实现)
func (m *MemoryModel) LoadDictHttp(urls ...string) error {
// 【标准库】创建带超时的客户端,防止请求卡死
client := &http.Client{
Timeout: 10 * time.Second, // 超时控制,非常重要
}
for _, url := range urls {
err := func(url string) error {
httpRes, err := req.Get(url)
// 立即执行函数,解决 defer 循环变量问题
err := func(u string) error {
// 标准库 GET 请求
resp, err := client.Get(u)
if err != nil {
return err
}
if httpRes == nil {
return errors.New("nil http response")
}
if httpRes.StatusCode != http.StatusOK {
return errors.New(httpRes.GetStatus())
return fmt.Errorf("请求失败 %s: %w", u, err)
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(httpRes.Body)
// 必须 defer 关闭 body防止资源泄漏标准库固定写法
defer func() {
closeErr := resp.Body.Close()
if closeErr != nil {
fmt.Printf("警告: 关闭响应体失败 url=%s, err=%v\n", u, closeErr)
}
}()
return m.LoadDict(httpRes.Body)
// 状态码判断
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("http 状态码错误 url=%s, code=%d", u, resp.StatusCode)
}
// 加载字典(和你原来逻辑一样)
return m.LoadDict(resp.Body)
}(url)
// 任意一个失败,立即返回
if err != nil {
return err
}

View File

@@ -2,11 +2,62 @@ package utils
import (
"errors"
"fmt"
"math/rand/v2"
"strings"
"time"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
)
// IsToday 判断给定时间是否是今天
func IsToday(t1 *gtime.Time) bool {
if t1 == nil {
return false
}
t := t1.Time
// 获取当前时间
now := time.Now()
// 比较年、月、日是否相同
return t.Year() == now.Year() &&
t.Month() == now.Month() &&
t.Day() == now.Day()
}
func IsWEEK(t1 *gtime.Time) bool {
if t1 == nil {
return false
}
t := t1.Time
// 获取当前时间
now := time.Now()
_, nweek := now.ISOWeek()
_, tweek := now.ISOWeek()
// 比较年、月、日是否相同
return t.Year() == now.Year() &&
tweek == nweek
}
func IsMon(t1 *gtime.Time) bool {
if t1 == nil {
return false
}
t := t1.Time
// 获取当前时间
now := time.Now()
nweek := now.Month()
tweek := now.Month()
// 比较年、月、日是否相同
return t.Year() == now.Year() &&
tweek == nweek
}
func FindWithIndex[T any](slice []T, predicate func(item T) bool) (int, *T, bool) {
for i := range slice {
if predicate(slice[i]) {
@@ -32,53 +83,71 @@ func RemoveLast(s string) string {
return string(runes[:len(runes)-1])
}
// ************************** 函数1核心函数双数组+int型概率泛型**************************
// randomByIntProbs 核心概率计算函数:接收任意类型元素数组 + int型概率数组实现权重随机
// T any支持任意类型的元素切片职责单一只处理纯int概率的核心逻辑
func RandomByIntProbs[T any](natureSet []T, probs []int) (T, error) {
// RandomByWeight 根据整数权重随机选择元素(优化后的泛型版本)
// 入参:
// - elements: 待随机的元素集合(非空)
// - weights: 对应元素的权重非负整数长度需与elements一致长度不匹配/总权重为0时降级为等概率随机
//
// 返回:
// - 随机选中的元素
// - 错误仅当elements为空/权重值为负时返回)
func RandomByWeight[Element any, Weight integer](elements []Element, weights []Weight) (Element, error) {
// 定义泛型零值,用于错误返回
var zeroT T
var zero Element
// 1. 合法性校验:元素数组为空 或 概率数组与元素数组长度不匹配
if len(natureSet) == 0 {
return zeroT, errors.New("natureSet is empty")
}
if len(probs) == 0 || len(natureSet) != len(probs) {
// 长度不匹配,降级为等概率随机(兼容原有逻辑)
return natureSet[grand.Intn(len(natureSet))], nil
// 1. 核心合法性校验:元素集合不能为空
if len(elements) == 0 {
return zero, errors.New("elements set is empty (cannot random from empty slice)")
}
// 2. 校验概率值非负,并计算总概率
totalProb := 0
for i, p := range probs {
if p < 0 {
return zeroT, errors.New("invalid prob value: index " + gconv.String(i) + " (must be non-negative integer)")
// 2. 权重数组合法性校验:长度不匹配/为空时,降级为等概率随机(兼容原逻辑)
elemLen := len(elements)
if len(weights) == 0 || len(weights) != elemLen {
return elements[rand.IntN(elemLen)], nil
}
// 3. 校验权重非负并计算总权重统一转为int64避免溢出
var totalWeight int64
// 预转换权重为int64避免重复类型转换
intWeights := make([]int64, elemLen)
for i, w := range weights {
intW := int64(w)
if intW < 0 {
return zero, fmt.Errorf("invalid negative weight at index %d (value: %d)", i, w)
}
totalProb += p
intWeights[i] = intW
totalWeight += intW
}
// 3. 总概率为0降级为等概率随机
if totalProb == 0 {
return natureSet[grand.Intn(len(natureSet))], nil
// 4. 总权重为0,降级为等概率随机
if totalWeight == 0 {
return elements[rand.IntN(elemLen)], nil
}
// 4. 计算前缀和(权重随机核心逻辑)
prefixSum := make([]int, len(probs))
prefixSum[0] = probs[0]
for i := 1; i < len(probs); i++ {
prefixSum[i] = prefixSum[i-1] + probs[i]
// 5. 计算前缀和(权重随机核心逻辑复用预转换的int64权重
prefixWeights := make([]int64, elemLen)
prefixWeights[0] = intWeights[0]
for i := 1; i < elemLen; i++ {
prefixWeights[i] = prefixWeights[i-1] + intWeights[i]
}
// 5. 生成随机数匹配前缀和,返回对应元素
randVal := grand.Intn(totalProb)
for i, sum := range prefixSum {
if randVal < sum {
return natureSet[i], nil
// 6. 生成随机数匹配前缀和用int64避免溢出
randomValue := grand.Intn(int(totalWeight))
for i, sum := range prefixWeights {
if randomValue < int(sum) {
return elements[i], nil
}
}
// 极端情况兜底,返回第一个元素
return natureSet[0], nil
// 极端兜底理论上不会走到这里randomValue < totalWeight返回第一个元素保证不panic
return elements[0], nil
}
// integer 自定义泛型约束:匹配所有整数类型(扩展原~int的限制
// 包含int/int8/int16/int32/int64/uint/uint8/uint16/uint32/uint64/uintptr
type integer interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
// ************************** 函数2封装函数兼容string型概率泛型**************************
@@ -89,7 +158,7 @@ func RandomByProbs[T any](natureSet []T, probs []string) (T, error) {
// 1. 若string概率数组为空直接调用核心函数核心函数会处理降级逻辑
if len(probs) == 0 {
return RandomByIntProbs(natureSet, []int{})
return RandomByWeight(natureSet, []int{})
}
// 2. string[] 转换为 int[](使用 gconv.Int 完成转换)
@@ -100,5 +169,42 @@ func RandomByProbs[T any](natureSet []T, probs []string) (T, error) {
}
// 3. 调用核心函数,复用概率计算逻辑
return RandomByIntProbs(natureSet, probInts)
return RandomByWeight(natureSet, probInts)
}
// IsCurrentTimeInRange 判断当前时间是否在 startStr 和 endStr 表示的时间区间内格式HH:MM
// 返回值true=在区间内false=不在区间内error=时间解析失败
func IsCurrentTimeInRange(startStr, endStr string) (bool, error) {
// 1. 解析开始和结束时间字符串为 time.Time 对象(日期用当前日期)
now := time.Now()
location := now.Location() // 使用当前时区,避免时区偏差
// 解析开始时间HH:MM
startTime, err := time.ParseInLocation("15:04", startStr, location)
if err != nil {
return false, fmt.Errorf("解析开始时间 %s 失败:%w", startStr, err)
}
// 解析结束时间HH:MM
endTime, err := time.ParseInLocation("15:04", endStr, location)
if err != nil {
return false, fmt.Errorf("解析结束时间 %s 失败:%w", endStr, err)
}
// 2. 把开始/结束时间的日期替换为当前日期(只保留时分)
startToday := time.Date(now.Year(), now.Month(), now.Day(),
startTime.Hour(), startTime.Minute(), 0, 0, location)
endToday := time.Date(now.Year(), now.Month(), now.Day(),
endTime.Hour(), endTime.Minute(), 0, 0, location)
// 3. 比较当前时间是否在 [startToday, endToday] 区间内
return now.After(startToday) && now.Before(endToday), nil
}
// Format 把 args 按顺序填入 {0},{1},{2}...
func Format(s string, args ...interface{}) string {
for i, arg := range args {
placeholder := "{" + string(rune('0'+i)) + "}"
s = strings.ReplaceAll(s, placeholder, gconv.String(arg))
}
return s
}

View File

@@ -56,9 +56,7 @@ type DoubleUInt8Struct struct {
}
func (di *DoubleUInt8) Pack(p []byte, opt *Options) (int, error) {
for i, value := range *di {
p[i] = value
}
copy(p, (*di)[:])
return 2, nil
}
@@ -131,9 +129,7 @@ type SliceUInt8Struct struct {
}
func (ia *SliceUInt8) Pack(p []byte, opt *Options) (int, error) {
for i, value := range *ia {
p[i] = value
}
copy(p, *ia)
return len(*ia) + 1, nil
}

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"math"
"reflect"
"unsafe"
)
type Field struct {
@@ -76,40 +77,60 @@ func (f *Field) Size(val reflect.Value, options *Options) int {
return size
}
func (f *Field) packVal(buf []byte, val reflect.Value, length int, options *Options) (size int, err error) {
// 预定义常用的order避免重复判断
var defaultOrder = binary.BigEndian
// packVal 优化版:减少反射开销+优化内存拷贝+优雅错误处理
func (f *Field) packVal(buf []byte, val reflect.Value, _ int, options *Options) (size int, err error) {
// 1. 预缓存order避免重复判断
order := f.Order
if options.Order != nil {
if options != nil && options.Order != nil {
order = options.Order
}
if f.Ptr {
val = val.Elem()
if order == nil {
order = defaultOrder
}
// 2. 处理指针类型:提前解引用,避免后续重复操作
if f.Ptr {
if !val.IsNil() {
val = val.Elem()
} else {
return 0, fmt.Errorf("field %s is nil pointer", f.Name)
}
}
// 3. 预解析类型避免重复Resolve
typ := f.Type.Resolve(options)
kind := val.Kind()
// 4. 扁平化分支逻辑,减少嵌套
switch typ {
case Struct:
return f.Fields.Pack(buf, val, options)
case Bool, Int8, Int16, Int32, Int64, Uint8, Uint16, Uint32, Uint64:
size = typ.Size()
if len(buf) < size {
return 0, fmt.Errorf("buf size %d < required %d", len(buf), size)
}
var n uint64
switch f.kind {
switch kind {
case reflect.Bool:
if val.Bool() {
n = 1
} else {
n = 0
}
n = boolToUint64(val.Bool())
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
n = uint64(val.Int())
default:
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
n = val.Uint()
default:
return 0, fmt.Errorf("unsupported kind %s for numeric type %s", kind, typ)
}
// 扁平化数值写入逻辑
switch typ {
case Bool:
if n != 0 {
buf[0] = 1
} else {
buf[0] = 0
}
buf[0] = byte(n)
case Int8, Uint8:
buf[0] = byte(n)
case Int16, Uint16:
@@ -117,33 +138,90 @@ func (f *Field) packVal(buf []byte, val reflect.Value, length int, options *Opti
case Int32, Uint32:
order.PutUint32(buf, uint32(n))
case Int64, Uint64:
order.PutUint64(buf, uint64(n))
order.PutUint64(buf, n)
}
case Float32, Float64:
size = typ.Size()
if len(buf) < size {
return 0, fmt.Errorf("buf size %d < required %d", len(buf), size)
}
if kind != reflect.Float32 && kind != reflect.Float64 {
return 0, fmt.Errorf("unsupported kind %s for float type %s", kind, typ)
}
n := val.Float()
switch typ {
case Float32:
order.PutUint32(buf, math.Float32bits(float32(n)))
case Float64:
order.PutUint64(buf, math.Float64bits(n))
}
case String:
switch f.kind {
// 优化String类型减少内存拷贝
switch kind {
case reflect.String:
s := val.String()
size = len(s)
if len(buf) < size {
return 0, fmt.Errorf("buf size %d < string length %d", len(buf), size)
}
// 用unsafe直接拷贝字符串到buf避免[]byte(s)的内存分配
copyStringToBuf(buf, s)
case reflect.Slice:
if val.Type().Elem().Kind() != reflect.Uint8 {
return 0, fmt.Errorf("unsupported slice type %s for String field", val.Type())
}
size = val.Len()
copy(buf, []byte(val.String()))
default:
// TODO: handle kind != bytes here
size = val.Len()
if len(buf) < size {
return 0, fmt.Errorf("buf size %d < bytes length %d", len(buf), size)
}
// 直接拷贝字节切片,避免冗余操作
copy(buf, val.Bytes())
default:
return 0, fmt.Errorf("unsupported kind %s for String type", kind)
}
case CustomType:
return val.Addr().Interface().(Custom).Pack(buf, options)
// 优化反射断言提前检查类型避免panic
if !val.CanAddr() {
return 0, fmt.Errorf("custom type %s cannot take address", val.Type())
}
custom, ok := val.Addr().Interface().(Custom)
if !ok {
return 0, fmt.Errorf("type %s does not implement Custom interface", val.Type())
}
return custom.Pack(buf, options)
default:
panic(fmt.Sprintf("no pack handler for type: %s", typ))
// 替换panic为error避免程序崩溃
return 0, fmt.Errorf("no pack handler for type: %s", typ)
}
return
return size, nil
}
// 辅助函数bool转uint64减少inline重复代码
func boolToUint64(b bool) uint64 {
if b {
return 1
}
return 0
}
// 辅助函数unsafe拷贝字符串到buf避免[]byte(s)的内存分配
// 注意仅在确定buf长度足够时使用
func copyStringToBuf(buf []byte, s string) {
// unsafe转换字符串转字节切片无内存分配
src := *(*[]byte)(unsafe.Pointer(&struct {
string
cap int
}{s, len(s)}))
copy(buf, src)
}
func (f *Field) Pack(buf []byte, val reflect.Value, length int, options *Options) (int, error) {
@@ -174,7 +252,6 @@ func (f *Field) Pack(buf []byte, val reflect.Value, length int, options *Options
copy(buf, buf[:length])
return length, nil
}
return val.Len(), nil
}
pos := 0
var zero reflect.Value
@@ -198,7 +275,7 @@ func (f *Field) Pack(buf []byte, val reflect.Value, length int, options *Options
}
}
func (f *Field) unpackVal(buf []byte, val reflect.Value, length int, options *Options) error {
func (f *Field) unpackVal(buf []byte, val reflect.Value, _ int, options *Options) error {
order := f.Order
if options.Order != nil {
order = options.Order

View File

@@ -2,6 +2,7 @@ package struc
import (
"encoding/binary"
"errors"
"fmt"
"io"
"reflect"
@@ -103,76 +104,188 @@ func (f Fields) Pack(buf []byte, val reflect.Value, options *Options) (int, erro
return pos, nil
}
// 提取魔法常量,便于配置和维护
const (
// MaxBufferSize 最大缓冲区大小,防止分配过大内存
MaxBufferSize = 1024 * 1024 // 1MB
// smallBufferSize 小缓冲区大小,复用栈上数组减少堆分配
smallBufferSize = 8
)
// -------------------------- 优化后的核心方法 --------------------------
func (f Fields) Unpack(r io.Reader, val reflect.Value, options *Options) error {
// 解引用指针,直到拿到非指针类型的值
for val.Kind() == reflect.Ptr {
val = val.Elem()
}
var tmp [8]byte
var buf []byte
for i, field := range f {
// 定义小缓冲区(栈上分配,减少堆内存开销)
var smallBuf [smallBufferSize]byte
var readBuf []byte
// 遍历所有字段
for fieldIdx, field := range f {
// 跳过空字段
if field == nil {
continue
}
v := val.Field(i)
length := field.Len
// 获取当前字段的反射值
fieldVal := val.Field(fieldIdx)
// 获取字段长度优先从Sizefrom读取否则用默认Len
fieldLen := field.Len
if field.Sizefrom != nil {
length = f.sizefrom(val, field.Sizefrom)
fieldLen = f.sizefrom(val, field.Sizefrom)
}
if v.Kind() == reflect.Ptr && !v.Elem().IsValid() {
v.Set(reflect.New(v.Type().Elem()))
// 处理指针字段:如果指针未初始化,创建新实例
if fieldVal.Kind() == reflect.Ptr && !fieldVal.Elem().IsValid() {
fieldVal.Set(reflect.New(fieldVal.Type().Elem()))
}
// 处理结构体类型字段
if field.Type == Struct {
if field.Slice {
vals := v
if !field.Array {
vals = reflect.MakeSlice(v.Type(), length, length)
}
for i := 0; i < length; i++ {
v := vals.Index(i)
fields, err := parseFields(v)
if err != nil {
return err
}
if err := fields.Unpack(r, v, options); err != nil {
return err
}
}
if !field.Array {
v.Set(vals)
}
} else {
// TODO: DRY (we repeat the inner loop above)
fields, err := parseFields(v)
if err != nil {
return err
}
if err := fields.Unpack(r, v, options); err != nil {
return err
}
if err := f.unpackStructField(r, fieldVal, fieldLen, field, options); err != nil {
return fmt.Errorf("unpack struct field index %d: %w", fieldIdx, err)
}
continue
}
// 处理非结构体类型字段(基础类型/自定义类型)
if err := f.unpackBasicField(r, fieldVal, field, fieldLen, smallBuf[:], &readBuf, options); err != nil {
return fmt.Errorf("unpack basic field index %d: %w", fieldIdx, err)
}
}
return nil
}
// 定义全局的最大安全切片长度(可根据业务调整,建议通过 options 配置)
const defaultMaxSafeSliceLen = 10000 // 1万根据实际场景调整
// 新增错误类型,便于上层捕获
var (
ErrExceedMaxSliceLen = errors.New("slice length exceeds maximum safe limit")
ErrInvalidSliceLen = errors.New("slice length is negative or zero")
)
// unpackStructField 抽离重复的结构体解析逻辑解决DRY问题
// 修复点:增加长度校验和内存分配防护
func (f Fields) unpackStructField(r io.Reader, fieldVal reflect.Value, length int, field *Field, options *Options) error {
// 修复1基础长度校验拒绝无效/超大长度
if length <= 0 {
return ErrInvalidSliceLen
}
// 修复2获取最大允许的切片长度优先使用 options 配置,无则用默认值)
maxSliceLen := defaultMaxSafeSliceLen
// 修复3校验长度是否超过安全阈值防止OOM
if length > maxSliceLen {
return fmt.Errorf("%w: requested %d, max allowed %d", ErrExceedMaxSliceLen, length, maxSliceLen)
}
// 处理切片/数组类型的结构体字段
if field.Slice {
var sliceVal reflect.Value
// 如果是数组(固定长度),直接使用原字段;如果是切片,创建指定长度的切片
if field.Array {
sliceVal = fieldVal
} else {
typ := field.Type.Resolve(options)
if typ == CustomType {
if err := v.Addr().Interface().(Custom).Unpack(r, length, options); err != nil {
return err
}
} else {
size := length * field.Type.Resolve(options).Size()
if size < 8 {
buf = tmp[:size]
} else {
buf = make([]byte, size)
}
if _, err := io.ReadFull(r, buf); err != nil {
return err
}
err := field.Unpack(buf[:size], v, length, options)
if err != nil {
return err
}
// 原逻辑这里是OOM的核心触发点现在已经提前做了长度校验
sliceVal = reflect.MakeSlice(fieldVal.Type(), length, length)
}
// 遍历切片/数组的每个元素,解析结构体
for elemIdx := 0; elemIdx < length; elemIdx++ {
elemVal := sliceVal.Index(elemIdx)
if err := f.unpackSingleStructElem(r, elemVal, options); err != nil {
return fmt.Errorf("slice elem %d: %w", elemIdx, err)
}
}
// 非数组类型需要将创建的切片赋值回原字段
if !field.Array {
fieldVal.Set(sliceVal)
}
} else {
// 处理单个结构体字段
if err := f.unpackSingleStructElem(r, fieldVal, options); err != nil {
return fmt.Errorf("single struct: %w", err)
}
}
return nil
}
// -------------------------- 抽离的辅助方法:处理单个结构体元素 --------------------------
// unpackSingleStructElem 解析单个结构体元素的核心逻辑(原重复代码)
func (f Fields) unpackSingleStructElem(r io.Reader, elemVal reflect.Value, options *Options) error {
// 解析结构体的字段定义
structFields, err := parseFields(elemVal)
if err != nil {
return fmt.Errorf("parse struct fields: %w", err)
}
// 递归调用Unpack解析结构体数据
if err := structFields.Unpack(r, elemVal, options); err != nil {
return fmt.Errorf("unpack struct elem: %w", err)
}
return nil
}
// -------------------------- 抽离的辅助方法:处理基础类型字段 --------------------------
// unpackBasicField 处理非结构体类型的字段(基础类型/自定义类型)
func (f Fields) unpackBasicField(
r io.Reader,
fieldVal reflect.Value,
field *Field,
length int,
smallBuf []byte,
readBuf *[]byte,
options *Options,
) error {
// 解析字段实际类型
fieldType := field.Type.Resolve(options)
// 处理自定义类型实现Custom接口
if fieldType == CustomType {
custom, ok := fieldVal.Addr().Interface().(Custom)
if !ok {
return errors.New("field does not implement Custom interface")
}
if err := custom.Unpack(r, length, options); err != nil {
return fmt.Errorf("custom unpack: %w", err)
}
return nil
}
// 计算需要读取的字节数
bufferSize := length * fieldType.Size()
// 检查缓冲区大小,防止内存溢出
if bufferSize > MaxBufferSize {
return fmt.Errorf("buffer size %d exceeds max %d", bufferSize, MaxBufferSize)
}
// 长度为0时直接返回
if bufferSize <= 0 {
return nil
}
// 复用小缓冲区(栈上)或分配堆缓冲区,减少内存分配开销
if bufferSize < smallBufferSize {
*readBuf = smallBuf[:bufferSize]
} else {
*readBuf = make([]byte, bufferSize)
}
// 读取指定长度的字节数据
if _, err := io.ReadFull(r, *readBuf); err != nil {
return fmt.Errorf("read data: %w", err)
}
// 解析字节数据到目标字段
if err := field.Unpack((*readBuf)[:bufferSize], fieldVal, length, options); err != nil {
return fmt.Errorf("field unpack: %w", err)
}
return nil
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"io"
"reflect"
"sync"
)
type Options struct {
@@ -33,31 +34,88 @@ func init() {
emptyOptions.Validate()
}
var prepCache = sync.Map{}
// cacheKey 缓存键:区分 结构体/自定义类型/二进制类型,保证缓存唯一性
type cacheKey struct {
typ reflect.Type // 数据的基础类型
kind uint8 // 0=结构体, 1=自定义类型, 2=二进制类型
}
// prep 优化版:带完整缓存,缓存处理后的最终 Packer
func prep(data interface{}) (reflect.Value, Packer, error) {
// 1. 提前判空
if data == nil {
return reflect.Value{}, nil, fmt.Errorf("Invalid reflect.Value for nil")
}
// 2. 初始反射值处理(和原逻辑一致)
value := reflect.ValueOf(data)
for value.Kind() == reflect.Ptr {
next := value.Elem().Kind()
elemValue := value
for elemValue.Kind() == reflect.Ptr {
next := elemValue.Elem().Kind()
if next == reflect.Struct || next == reflect.Ptr {
value = value.Elem()
elemValue = elemValue.Elem()
} else {
break
}
}
switch value.Kind() {
// 3. 构建缓存键的基础类型(取解引用后的类型)
baseType := elemValue.Type()
var packer Packer
var err error
// 4. 按类型分支处理,优先查缓存
switch elemValue.Kind() {
case reflect.Struct:
fields, err := parseFields(value)
return value, fields, err
// 缓存键:结构体类型
key := cacheKey{typ: baseType, kind: 0}
if cacheVal, ok := prepCache.Load(key); ok {
// 缓存命中:直接返回缓存的 Packer
return elemValue, cacheVal.(Packer), nil
}
// 缓存未命中:执行原逻辑解析 fields
packer, err = parseFields(elemValue)
if err != nil {
return elemValue, nil, err
}
// 缓存处理后的 Packer
prepCache.Store(key, packer)
default:
if !value.IsValid() {
// 非结构体类型:检查有效性
if !elemValue.IsValid() {
return reflect.Value{}, nil, fmt.Errorf("Invalid reflect.Value for %+v", data)
}
if c, ok := data.(Custom); ok {
return value, customFallback{c}, nil
}
return value, binaryFallback(value), nil
}
}
// 自定义类型分支
if c, ok := data.(Custom); ok {
// 缓存键:自定义类型
key := cacheKey{typ: baseType, kind: 1}
if cacheVal, ok := prepCache.Load(key); ok {
return elemValue, cacheVal.(Packer), nil
}
// 构建 customFallback 并缓存
// 仅用 custom Custom 构建,完全匹配你的定义
packer = customFallback{custom: c}
prepCache.Store(key, packer)
} else {
// 二进制类型分支
// 缓存键:二进制类型
key := cacheKey{typ: baseType, kind: 2}
if cacheVal, ok := prepCache.Load(key); ok {
return elemValue, cacheVal.(Packer), nil
}
// 构建 binaryFallback 并缓存
packer = binaryFallback(elemValue)
prepCache.Store(key, packer)
}
}
// 5. 返回和原逻辑完全一致的结果
return elemValue, packer, err
}
func Pack(w io.Writer, data interface{}) error {
return PackWithOptions(w, data, nil)
}

View File

@@ -8,6 +8,7 @@ import (
"testing"
"github.com/apcera/termtables"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/glog"
)
@@ -37,6 +38,12 @@ type Map struct {
Galaxy string `xml:"galaxy,attr" json:"galaxy,omitempty"`
}
func Mapxml() {
superMaps := &SuperMaps{}
err := xml.Unmarshal([]byte(gfile.GetBytes("public/config/地图配置野怪.xml")), superMaps)
fmt.Println(err)
}
func TestXml(t *testing.T) {
// 示例XML数据
xmlData := `<?xml version="1.0" encoding="UTF-8"?>

View File

@@ -84,3 +84,18 @@ func RandomSlice[T any](slice []T, n int) []T {
return result
}
// T: 切片元素类型必须是可比较类型满足map键的要求
// 返回值map[T]int - 键为切片元素,值为对应出现次数
func CountSliceElements[T comparable](slice []T) map[T]int {
// 初始化map预设容量为切片长度优化性能
countMap := make(map[T]int, len(slice))
// 遍历切片,统计每个元素的出现次数
for _, v := range slice {
// 若元素已存在,值+1不存在则自动初始化为0后+1
countMap[v]++
}
return countMap
}

177
common/utils/zset/README.md Normal file
View 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
View File

@@ -0,0 +1,3 @@
module github.com/liwnn/zset
go 1.18

540
common/utils/zset/zset.go Normal file
View 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)
}

View 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
}

View 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
})
}
}

View 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
})
}
}

View File

@@ -0,0 +1,183 @@
# Boss ScriptHookAction接入说明
日期2026-04-05
## 1. 执行流程
1. 先执行战斗效果链 `HookAction()`
2. 执行脚本 `hookAction(hookaction)`
3. 用脚本返回值决定是否继续出手
4. 脚本可直接调用 Go 绑定函数`useSkill()``switchPet()`
## 2. JS 可调用的 Go 函数
1. `useSkill(skillId: number)`
2. `switchPet(catchTime: number)`
## 3. `hookaction` 参数字段
基础字段
1. `hookaction.hookaction: boolean`
2. `hookaction.round: number`
3. `hookaction.is_first: boolean`
4. `hookaction.our: { pet_id, catch_time, hp, max_hp } | null`
5. `hookaction.opp: { pet_id, catch_time, hp, max_hp } | null`
6. `hookaction.skills: Array<{ skill_id, pp, can_use }>`
AttackValue 映射字段重点
1. `hookaction.our_attack`
2. `hookaction.opp_attack`
结构
```ts
{
skill_id: number;
attack_time: number;
is_critical: number;
lost_hp: number;
gain_hp: number;
remain_hp: number;
max_hp: number;
state: number;
offensive: number;
status: number[]; // 对应 AttackValue.Status[20]
prop: number[]; // 对应 AttackValue.Prop[6]
}
```
其中
- `prop` 索引`[攻, 防, 特攻, 特防, 速度, 命中]`
- 对应值 `> 0` 代表强化`< 0` 代表下降`0` 代表无变化
返回值
- `true`继续行动
- `false`阻止行动
- 不返回默认回退到 `hookaction.hookaction`
## 4. 脚本示例
### 4.1 判断对方是否存在强化你问的这个
```js
function hookAction(hookaction) {
if (!hookaction.hookaction) return false;
var oppAtk = hookaction.opp_attack;
var oppHasBuff = false;
if (oppAtk && oppAtk.prop) {
for (var i = 0; i < oppAtk.prop.length; i++) {
if (oppAtk.prop[i] > 0) {
oppHasBuff = true;
break;
}
}
}
if (oppHasBuff) {
// 对方有强化时,放一个针对技能
useSkill(5001);
return true;
}
return true;
}
```
### 4.2 判断对方是否有异常状态
```js
function hookAction(hookaction) {
if (!hookaction.hookaction) return false;
var oppAtk = hookaction.opp_attack;
var hasStatus = false;
if (oppAtk && oppAtk.status) {
for (var i = 0; i < oppAtk.status.length; i++) {
if (oppAtk.status[i] > 0) {
hasStatus = true;
break;
}
}
}
if (!hasStatus) {
// 没有异常时尝试上异常
useSkill(6002);
}
return true;
}
```
## 5. Monaco 类型提示
```ts
import * as monaco from "monaco-editor";
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
allowNonTsExtensions: true,
checkJs: true,
target: monaco.languages.typescript.ScriptTarget.ES2020,
});
monaco.languages.typescript.javascriptDefaults.addExtraLib(
`
interface BossHookPetContext {
pet_id: number;
catch_time: number;
hp: number;
max_hp: number;
}
interface BossHookSkillContext {
skill_id: number;
pp: number;
can_use: boolean;
}
interface BossHookAttackContext {
skill_id: number;
attack_time: number;
is_critical: number;
lost_hp: number;
gain_hp: number;
remain_hp: number;
max_hp: number;
state: number;
offensive: number;
status: number[];
prop: number[];
}
interface BossHookActionContext {
hookaction: boolean;
round: number;
is_first: boolean;
our: BossHookPetContext | null;
opp: BossHookPetContext | null;
skills: BossHookSkillContext[];
our_attack: BossHookAttackContext | null;
opp_attack: BossHookAttackContext | null;
}
declare function hookAction(hookaction: BossHookActionContext): boolean;
declare function HookAction(hookaction: BossHookActionContext): boolean;
declare function hookaction(hookaction: BossHookActionContext): boolean;
declare function useSkill(skillId: number): void;
declare function switchPet(catchTime: number): void;
`,
"ts:boss-script.d.ts"
);
```
## 6. 后端代码
- 脚本执行器与函数绑定`modules/config/model/boss_pet.go`
- AI 出手转发与上下文构建`logic/service/fight/input/ai.go`

View File

@@ -0,0 +1,400 @@
# 战斗系统对齐 `flash/group` 组队战斗实施清单执行版
日期2026-04-04
适用仓库`E:\newcode\sun`
参考客户端仓库`E:\newcode\flash`
---
## 1. 结论与范围
### 1.1 结论
- `sun` 当前战斗系统具备多战位骨架`ActorIndex/TargetIndex``Our/Opp []*input.Input`但未完成组队战斗全链路
- `flash` `group` 分支当前 HEAD 已回滚组队重构组队实现主要存在于历史提交 `4c07fa07`
- 因此本次不是直接搬代码而是按协议与行为对齐实现
### 1.2 本清单目标
- 在不破坏现有 `1v1` 的前提下落地组队战斗可运行版本MVP
- 对齐 `flash`/社区实现中的关键行为开战出招切宠道具结算战斗结束
- 协议层采用一个统一结构体 + phase 字段方案单打/双打共用同一序列化模型
- 保留旧 `24xx/25xx` 流程入口通过服务端适配映射到统一结构体
### 1.3 非目标
- 不要求一次性 100% 复刻客户端所有 UI/演出细节
- 不要求一次性改完全部 effect先保证核心流程可跑再分批清理
---
## 2. 基线事实实施前必须统一认知
### 2.1 `flash` 仓库事实
- `group` 分支相对 `main` 的提交
- `4c07fa07 refactor(group-fight)`引入组队
- `a410bfca Revert "refactor(group-fight)"`回滚组队
- `e2382a4f`地图重构
- `bd84f206`.gitignore
- 所以 `group` HEAD 不再包含 `GroupFightDLL``core/group/*` 组队代码需参考 `4c07fa07` 的内容
### 2.2 `sun` 战斗现状
- 已有多战位骨架
- `logic/service/fight/input.go``Our/Opp []*input.Input`
- `logic/service/fight/action/BattleAction.go``ActorIndex/TargetIndex`
- `logic/service/fight/new_options.go``WithFightPlayersOnSide/WithFightInputs`
- 仍有关键缺口
- 控制器入站仍是单战位参数 `2405/2406/2407` 只传技能/道具/catchTime
- 回合主链仍以双动作兼容流程为中心
- 组队相关特性存在 TODO例如 `501/502/503`
### 2.3 外部实现参考本次新增
- `arcadia-star/seer2-fight-ui`
- 双打核心模型不是独立命令集而是统一帧模型 + `uiStyle + side + position`
- `uiStyle` 支持 `2v2/2v1`战位通过 `position(main/sub)` 区分
- `arcadia-star/seer2-next-message/src/entity/fight.rs`
- 采用统一战斗实体结构`team/user/pet` + `side/position`
- 行为包拆分为 `Load/Hurt/Change/Escape/...`但底层字段模型统一
- `ukuq/seer2-server/src/seer2/fight`
- `ArenaResourceLoadCMD -> TeamInfo -> FightUserInfo -> FighterInfo` 为层级化统一结构
- `FighterInfo` 直接包含 `position/hp/maxHp/anger/skills`适合直接映射为本项目统一结构体
---
## 3. 协议对齐清单按优先级
> 说明本清单改为统一协议结构体路线不再强制先实现 `75xx` 独立命令族
> 推荐做法保留旧入口命令服务端内部统一转为 `FightActionEnvelope/FightStateEnvelope`
### 3.1 P0 必做MVP 必须
- [ ] 统一入站动作结构 `FightActionEnvelope`
- 最少字段`actionType/actorIndex/targetIndex/skillId/itemId/catchTime/escape/chat`
- 兼容映射
- `2405 -> actionType=skill`
- `2406 -> actionType=item`
- `2407 -> actionType=change`
- `2410 -> actionType=escape`
- [ ] 统一出站状态结构 `FightStateEnvelope`
- 最少字段
- `phase``start/skill_hurt/change/over/load/chat`
- `left[]/right[]`元素为统一 `FighterState`
- `meta`回合号天气胜负结束原因
- [ ] 统一战位子结构 `FighterState`
- 每项至少包含`side/position(userSlot)/userId/petId(catchTime)/hp/maxHp/level/anger/status/prop/skills`
### 3.2 P1 强烈建议提升一致性
- [ ] 完善 `phase=skill_hurt`
- 至少带施法方快照受击方快照技能暴击伤害HP 变更
- [ ] 完善 `phase=change`
- 至少带切宠发起位切入目标位新精灵状态
- [ ] 完善 `phase=over`
- 至少带结束原因胜方收益主体
- [ ] 完善 `phase=load/chat`
- 组队加载进度战斗内聊天统一走同一 envelope
### 3.3 P2 视时间补齐
- [ ] `phase=sprite_die/sprite_notice/win_close`
- [ ] `phase=skill_wait/skill_wait_notice`
- [ ] `phase=overtime/timeout_exit/relation_notice`
---
## 4. 代码改造任务清单可直接分工
## 4.1 协议与结构层Owner A
- [ ] 新增统一协议结构文件
- 建议新建`logic/service/fight/cmd_unified.go`
- 要求统一定义 `FightActionEnvelope` 和映射辅助结构
- [ ] 新增统一出站结构
- 建议新建`logic/service/fight/info/unified_info.go`
- 要求定义 `FightStateEnvelope/FighterState`支持单打与双打
- [ ] 统一战位字段命名规范
- `actorIndex`我方执行位
- `targetIndex`敌方目标位
- `side+pos` `actorIndex/targetIndex` 转换规则写入注释
验收
- [ ] cmd`2405/2406/2407/2410`可无损映射到统一入站结构
- [ ] 统一出站结构在 `start/skill_hurt/change/over` phase 均可序列化
---
## 4.2 控制器与路由层Owner B
- [ ] 新增统一动作入口可单文件
- 建议新建`logic/controller/fight_unified.go`
- 用途将旧包和未来扩展包统一落到 `FightActionEnvelope`
- [ ] 兼容旧协议入口
- `2405/2406/2407` 保持可用默认 `actorIndex=0,targetIndex=0`
- 组队场景由 `actorIndex/targetIndex` 与战斗上下文决定不再依赖独立 `75xx`
- [ ] 增加战前校验
- 成员是否在同一组队房间
- 战斗状态互斥
- 战位可操作权限
验收
- [ ] 任意技能动作都能转化为 `UseSkillAt(...)` `actorIndex/targetIndex`
- [ ] 非法战位命令被拒绝不影响其他战位
---
## 4.3 战斗核心层Owner C
- [ ] 固化多动作一回合模型
- `collectPlayerActions`按预期战位数收集不是按两人收集
- `resolveRound`每回合一次统一排序与执行
- [ ] 降低对双动作 enterturn的耦合
- 当前 `enterturn(first, second)` 作为兼容层保留
- 新逻辑要确保
- 回合开始钩子只执行一次/回合
- 回合结束钩子只执行一次/回合
- 不因 pair 分片导致重复触发
- [ ] 完善动作-战位映射
- `GetInputByAction` 在组队模式下严格按 `playerID + actorIndex/targetIndex` 定位
- 超时补默认动作按战位补齐
- [ ] 完善死亡换宠/主动换宠
- actorIndex 粒度处理
- 切宠广播必须携带 actor 位信息
验收
- [ ] 2v2 场景一回合四动作都参与排序不丢动作
- [ ] 同玩家多战位动作不会互相覆盖
- [ ] 任一战位死亡只影响对应战位换宠链路
---
## 4.4 组队战报与广播层Owner D
- [ ] 统一战报快照结构
- 至少包含
- 施法方`userId/actorIndex/skillId/crit/dmg/hpAfter/status/prop`
- 受击方`userId/actorIndex/hpAfter/status/prop`
- [ ] 完成关键广播
- 开战广播
- 技能结果广播
- 切宠成功广播
- 战斗结束广播
- [ ] 保留旧包兼容必要时双发
- 单打/双打统一走同一结构体
- 如前端未升级可按需保留旧 `2503/2505/2506` 过渡映射
验收
- [ ] 观战端/队友端收到的战位与 HP 同步一致
- [ ] 切宠后不会出现错位显示
---
## 4.5 Effect 与规则层Owner E
- [ ] 先补明确组队依赖效果
- `logic/service/fight/boss/NewSeIdx_501.go`
- `logic/service/fight/boss/NewSeIdx_502.go`
- `logic/service/fight/boss/NewSeIdx_503.go`
- [ ] 统一队友查询工具函数
- 建议在 `input` `fight` 层提供
- 获取同阵营存活战位
- 获取队友列表排除自己
- 群体目标选择上限
- [ ] 扫描组队敏感 effect
- 特别关注含组队对战时无效描述项 effect 457
- 明确是直接禁用还是按组队模式替代逻辑
验收
- [ ] `501/502/503` 2v2 场景行为符合设计
- [ ] 组队模式下不再出现空指针或越界
---
## 4.6 测试与回归Owner F
- [ ] 单测补齐
- `logic/service/fight/action_test.go`继续扩充多战位覆盖
- 新增建议
- `logic/service/fight/loop_multi_test.go`
- `logic/service/fight/fight_group_test.go`
- [ ] 集成回归用例最少
- Case 11v1 旧流程
- Case 22v2 双方四动作
- Case 3同一玩家两战位各自出招
- Case 4中途切宠 + 被动死亡切宠
- Case 5超时默认动作补齐
- Case 6逃跑/掉线结束
- [ ] 构建与测试命令
- `cd logic && go test ./service/fight/...`
- `cd logic && go test ./controller/...`
- `cd logic && go build ./...`
---
## 5. 文件级任务地图便于派工
- 协议/结构
- `logic/service/fight/cmd.go`
- `logic/service/fight/cmd_unified.go`新增
- `logic/service/fight/info/info.go`
- `logic/service/fight/info/unified_info.go`新增
- 控制器
- `logic/controller/fight_base.go`
- `logic/controller/fight_pvp_withplayer.go`
- `logic/controller/fight_unified.go`新增
- 核心流程
- `logic/service/fight/new.go`
- `logic/service/fight/new_options.go`
- `logic/service/fight/input.go`
- `logic/service/fight/action.go`
- `logic/service/fight/loop.go`
- `logic/service/fight/fightc.go`
- Effect
- `logic/service/fight/boss/NewSeIdx_501.go`
- `logic/service/fight/boss/NewSeIdx_502.go`
- `logic/service/fight/boss/NewSeIdx_503.go`
- 其他含组队语义的 effect 文件
- 测试
- `logic/service/fight/action_test.go`
- `logic/service/fight/*_test.go`新增
---
## 6. 里程碑与交付标准
### M1协议可通
- [ ] 统一结构体可完成 `start/skill_hurt/change/over` 四类下发
- [ ] 旧命令入口均可映射到 `FightC` indexed 接口
### M2核心可跑
- [ ] 2v2 全回合可稳定执行
- [ ] 切宠/道具/超时可用
### M3规则可用
- [ ] 501/502/503 完成
- [ ] 主要组队战报可用
### M4回归上线
- [ ] 1v1 不回归
- [ ] `go test` `go build` 通过
- [ ] 文档补充已完成项与遗留项
---
## 7. 风险清单与缓解
- 风险旧逻辑大量默认 `CurPet[0]`多人战位容易错位
缓解引入统一 `CurrentPetByActor`/`TargetByIndex` 访问函数禁止新代码直接写死 `[0]`
- 风险`enterturn` 兼容层导致钩子重复触发
缓解回合开始/结束 pair 执行中抽离确保每回合只触发一次
- 风险协议切换导致旧客户端不可用
缓解服务端保持旧入口不变先做旧包 -> 统一结构映射前端按版本切流
- 风险effect 批量改动引发回归
缓解先做组队关键 effect其他 effect 分批迁移并每批回归
---
## 8. 实施顺序建议最小阻塞
1. 协议结构与控制器入口
2. 动作收集与回合统一执行
3. 切宠/道具/超时按战位修正
4. 关键广播与战报
5. 组队 effect501/502/503
6. 全量测试与回归
---
## 9. 交接要求给执行同学
- 每完成一个里程碑 `docs/` 新增一段完成项/未完成项/阻塞项
- 如改动协议字段必须附抓包样例或字段注释不允许只改代码不补说明
- 如发现与本清单冲突的历史逻辑兼容线上行为优先并在文档记录偏差原因
---
## 10. 可实现性结论统一协议结构体
- 结论可实现且风险可控
- 依据
- `seer2-fight-ui` 的双打模型本质是统一数据结构 + `uiStyle/side/position`不是强依赖独立命令族
- `seer2-next-message` `seer2-server` 都采用统一 `team/user/pet` 层级结构`position` 作为战位核心字段
- 本仓库已具备 `actorIndex/targetIndex` `UseSkillAt/ChangePetAt/UseItemAt` 能力协议统一后只需补齐映射和广播
- 实施建议
- 先完成旧入口 -> 统一入站结构映射
- 再完成统一出站结构 + phase 广播
- 最后做前端切换与旧包退场或长期双通道兼容
---
## AtkType 目标语义补充2026-04-05
来源`flash` `SkillXMLInfo.getGpFtSkillType(skillID)`读取 `movesMap/moveStoneMap` `AtkType`
GBTL 规则已确认
1. `AtkNum`本技能同时攻击数量默认 `1`不能为 `0`
2. `AtkType`目标范围
- `0`所有人
- `1`仅己方
- `2`仅对方
- `3`仅自己
- 默认`2`
前端目标选择行为`SkillMouseController.attack(skillID, attackType)`
1. `attackType=0` -> `allPetWinList`全体可选
2. `attackType=1` -> `membPetWinList`己方可选含自己与队友
3. `attackType=2` -> `oppPetWinList`敌方可选
4. `attackType=3` -> `[playerMode.petWin]`仅自己
后端目标关系判定组队/多战位必须遵循
1. 若协议传 `actor + target(side,pos)`
- `target.side != actor.side` => 对方目标
- `target.side == actor.side && target.pos == actor.pos` => 自身目标
- `target.side == actor.side && target.pos != actor.pos` => 队友目标
2. 若协议未显式传目标 `2405`
- `AtkType` 兜底
- `AtkType=3` => 强制自身
- `AtkType=1` => 默认自身无显式队友位时
- 其他 => 维持旧行为默认对方 `0`
实施要求与现有清单并行
1. `common/data/xmlres/skill.go` `Move` 需包含 `AtkType` 字段解析
2. 动作目标不再依赖默认 Opp 绑定effect 上下文必须使用本次动作的实际目标
3. 需支持区分 `self` `ally`例如同为 `AtkType=1` 不能混用同一默认目标
4. 保持旧协议兼容旧入口不报错但按上述兜底规则执行

View File

@@ -0,0 +1,194 @@
# Fight Input 控制绑定说明
日期2026-04-04
## 1. 背景
当前战斗模型中一个 `Input` 对应一个战斗站位`actorIndex`
每个 `Input` 通过 `Input.Player` 绑定操作者
当前建战主路径已收敛为`WithFightInputs(ourInputs, oppInputs)`
先由调用方创建并组装双方 `Input`再传给战斗模块
为了同时支持以下两种玩法新增了可配置绑定策略
1. 双打一个玩家控制多个站位单人多 `Input`
2. 组队一个玩家控制一个站位每人一个 `Input`
## 2. 绑定策略
文件`logic/service/fight/new_options.go`
- `InputControllerBindingKeep`
- 含义保持输入中已有 `Input.Player` 绑定不覆盖
- 适用调用方已手动构造 `Input` 绑定
- `InputControllerBindingSingle`
- 含义单侧全部站位统一绑定为 `players[0]`
- 适用双打中一个人控制多个站位
- `InputControllerBindingPerSlot`
- 含义按站位顺序绑定为 `players[i]`
- 适用组队中一人一个站位
- 说明 `players` 数量不足时回退绑定 `players[0]`
## 3. 选项接口
文件`logic/service/fight/new_options.go`
新增选项
```go
WithInputControllerBinding(mode int)
```
## 4. 生效时机
文件`logic/service/fight/new.go`
`buildFight` 构建完 `Our/Opp` 输入后先执行控制绑定再执行上下文绑定
1. `bindInputControllers(f.Our, f.OurPlayers, opts.controllerBinding)`
2. `bindInputControllers(f.Opp, f.OppPlayers, opts.controllerBinding)`
3. `bindInputFightContext(...)`
4. `linkTeamViews()`
5. `linkOppInputs()`
## 5. 使用示例
### 5.1 双打单人控多站位
```go
fight.NewFightWithOptions(
fight.WithFightPlayersOnSide(
[]common.PlayerI{ourPlayer},
[]common.PlayerI{oppPlayer},
),
fight.WithFightInputs(ourInputs, oppInputs),
fight.WithInputControllerBinding(fight.InputControllerBindingSingle),
)
```
### 5.2 组队一人一个站位
```go
fight.NewFightWithOptions(
fight.WithFightPlayersOnSide(
[]common.PlayerI{ourP1, ourP2},
[]common.PlayerI{oppP1, oppP2},
),
fight.WithFightInputs(ourInputs, oppInputs),
fight.WithInputControllerBinding(fight.InputControllerBindingPerSlot),
)
```
### 5.3 仅传已绑定 Input推荐灵活接入
```go
ourInputs := []*input.Input{
input.NewInput(nil, ourP1), // 站位0
input.NewInput(nil, ourP2), // 站位1
}
oppInputs := []*input.Input{
input.NewInput(nil, oppP1), // 站位0
input.NewInput(nil, oppP2), // 站位1
}
fc, err := fight.NewFightWithOptions(
fight.WithFightInputs(ourInputs, oppInputs),
// 不传 WithFightPlayersOnSide 也可
// owner/opponent 与 side players 会从 inputs 自动提取
)
_ = fc
_ = err
```
说明`InputControllerBindingSingle/PerSlot` 会覆盖 `ourInputs/oppInputs` 中原有的 `Input.Player` 绑定`Keep` 不覆盖
## 6. 新模式绑定实例逐模式
以下示例假设我方有两个站位`ourInputs[0]``ourInputs[1]`
### 6.1 Keep保持输入原绑定
调用
```go
fight.NewFightWithOptions(
fight.WithFightInputs(ourInputs, oppInputs),
fight.WithInputControllerBinding(fight.InputControllerBindingKeep),
)
```
输入调用前
- `ourInputs[0].Player = ourP1`
- `ourInputs[1].Player = ourP2`
结果调用后
- `ourInputs[0].Player = ourP1`
- `ourInputs[1].Player = ourP2`
适用调用方已提前把每个站位绑定好不希望框架覆盖
### 6.2 Single单人控制全部站位
调用
```go
fight.NewFightWithOptions(
fight.WithFightPlayersOnSide(
[]common.PlayerI{ourCaptain},
[]common.PlayerI{oppCaptain},
),
fight.WithFightInputs(ourInputs, oppInputs),
fight.WithInputControllerBinding(fight.InputControllerBindingSingle),
)
```
输入调用前
- `ourInputs[0].Player = ourP1`
- `ourInputs[1].Player = ourP2`
结果调用后
- `ourInputs[0].Player = ourCaptain`
- `ourInputs[1].Player = ourCaptain`
适用双打或多站位由同一玩家操作
### 6.3 PerSlot按站位顺序绑定玩家
调用
```go
fight.NewFightWithOptions(
fight.WithFightPlayersOnSide(
[]common.PlayerI{ourP1, ourP2},
[]common.PlayerI{oppP1, oppP2},
),
fight.WithFightInputs(ourInputs, oppInputs),
fight.WithInputControllerBinding(fight.InputControllerBindingPerSlot),
)
```
输入调用前
- `ourInputs[0].Player = anyA`
- `ourInputs[1].Player = anyB`
结果调用后
- `ourInputs[0].Player = ourP1`
- `ourInputs[1].Player = ourP2`
补位规则 `players` 数量不足例如只传一个 `ourP1`剩余站位回退绑定 `players[0]`
## 7. 注意事项
1. 默认模式是 `InputControllerBindingKeep`不影响现有调用
2. 若传入 `WithFightInputs(...)` 且每个 `Input.Player` 已预先绑定可继续用默认模式
3. 仅传 `WithFightInputs(...)` 也可工作框架会从 `ourInputs/oppInputs` 自动提取 `ourPlayers/oppPlayers`并以各侧首位玩家作为 owner/opponent
4. 推荐在新组队逻辑中显式传 `WithInputControllerBinding(...)`避免调用方歧义

View File

@@ -0,0 +1,327 @@
# 战斗多单位模式改造交接文档2026-04-04
## 0. 2026-04-04 本轮完成情况
本轮已完成以下落地项
- 动作提交改为按 `playerID + actorIndex` 去重同一玩家同回合的多个上场位动作不会再互相覆盖
- 主循环已从双动作入口改为动作列表入口`resolveRound` 现接收并处理 `[]action.BattleActionI`
- 回合结算增加了基于优先级和速度的统一排序并按跨阵营动作对子顺序执行保留现有 `enterturn(first, second)` 兼容层
- 技能和道具的目标选择已接入 `targetIndex`不再固定打对面 `0` 号位
- 切宠同步改为携带 `actorIndex`同一玩家多上场位的切宠播报不再冲突
- 开战同步结构新增当前战斗位数组同时保留 `Info1/Info2` 兼容旧结构
- `FightI` 已补充 `UseSkillAt/ChangePetAt/UseItemAt/GetCurrPETAt`
- `NewFight` 已改为包装 `NewFightWithOptions(...)`创建阶段开始支持 option/builder 扩展
- `Ctx` 已拆分为 `LegacySides + EffectBinding`effect 本体上挂载上下文并补充 `Source/Carrier/Target`
- 核心执行链已开始迁移到真实 source/target 语义`AddEffect``Exec``Damage``SetProp`主技能结算流程会注入实际对手上下文
- 已迁移一批公共/高复用 effect 到新语义包括状态基类击败触发物攻附加状态`1097-1101``680-690`部分魂印基础逻辑
- 本轮继续完成了 `1263-1287``1288-1312``1448-1472``1473-1497` 四组 effect 的迁移已不再直接依赖 `Ctx().Our/Opp`统一改为 `CarrierInput()/OpponentInput()` 访问当前承载侧与对位侧
- 增加了动作队列的基础单测覆盖同玩家不同槽位保留同槽位动作替换
本轮仍保留的限制
- `enterturn` 和大量 `effect/node` 逻辑仍是双动作上下文因此当前实现采用动作列表排序 + 跨阵营配对兼容执行的过渡方案而不是一次性重写所有效果系统
- `NewFight` 仍按现有建房流程创建双方 1 个战斗位本轮打通的是多战斗位结算骨架和接口不是外部建房入口的全量切换
- 大量具体 effect 仍在使用旧的 `Ctx().Our/Opp` 语义当前已迁移的是上下文承载方式执行链和部分公共基类具体 effect 仍需继续分批迁移
### 0.1 effect 迁移增量记录
本轮新增完成
- `logic/service/fight/effect/1263_1287.go`
- `logic/service/fight/effect/1288_1312.go`
- `logic/service/fight/effect/1448_1472.go`
- `logic/service/fight/effect/1473_1497.go`
这两组文件当前迁移策略是
- 旧语义中的 `Our` 统一视为当前执行/承载该 effect 的输入侧迁移为 `CarrierInput()`
- 旧语义中的 `Opp` 统一迁移为当前结算上下文里的对位输入侧 `OpponentInput()`
- 暂不在这一轮强行把所有 effect 重写成纯 `Source/Target/Carrier` 三元语义先保证 hostile sub-effect回合类 effect挂在对手身上的限制类 effect 都不再依赖 legacy 字段访问
### 0.2 下一批待迁移队列
高密度遗留文件已继续向后推进`1448-1497` 这两个分段本轮已清理完成
下一轮继续迁移时建议直接对 effect 包执行一次全量扫描仍包含 `Ctx().Our/Opp` grouped file继续往后收口而不是再只盯固定编号段
## 1. 任务目标
将当前战斗系统从每回合双方各 1 个动作的模型改造成支持多上场位多操作者的统一回合模型最终支持以下 3 种战斗模式
1. `1玩家:N精灵:1上场 VS 1玩家:N精灵:1上场`
2. `N玩家:N精灵:N上场 VS N玩家:N精灵:N上场`
3. `1玩家:N精灵:N上场 VS 1玩家:N精灵:N上场`
当前代码只完整支持模式 1模式 2 和模式 3 只做了结构铺垫还没有真正打通
---
## 2. 当前已完成的基础改造
以下结构改造已经落地
- `FightC.Our/Opp` 已改成数组表示战场单位数组不再是单对象
- `input.Input.CurrentPet` 已改成 `CurPet`并且是数组
- `FightC.OurPlayers/OppPlayers` 已加入用于表达操作者数组
- 战斗单位与操作者已解耦
- `Our/Opp` 表示战斗位
- `OurPlayers/OppPlayers` 表示操作这些战斗位的玩家
- `BattlePetEntity` 已支持绑定控制者`ControllerUserID`
- 动作模型已支持
- `ActorIndex`
- `TargetIndex`
- 已提供 indexed 入口
- `UseSkillAt(c, skillID, actorIndex, targetIndex)`
- `ChangePetAt(c, petID, actorIndex)`
- `UseItemAt(c, catchTime, itemID, actorIndex, targetIndex)`
当前默认行为仍等价于
- `actorIndex = 0`
- `targetIndex = 0`
也就是当前模式下仍然是操作和结算 `0` 号单位
---
## 3. 当前未完成的核心问题
### 3.1 回合模型仍然是双动作模型
目前主流程仍然是每回合只处理双方两个动作而不是处理一个动作列表
关键位置
- `logic/service/fight/loop.go`
- `collectPlayerActions(...)` 只收 2 个动作
- `resolveRound(p1Action, p2Action)` 只结算 2 个动作
这意味着
- 模式 2 无法支持双方多个操作者或多个上场位同时行动
- 模式 3 无法支持同一玩家控制多个上场位分别出手
### 3.2 动作提交仍按 `playerID` 去重
当前动作队列逻辑仍以 `playerID` 作为主要识别维度
关键位置
- `logic/service/fight/action.go`
- `submitAction(...)`
这会导致
- 同一玩家在同一回合给多个上场位下达动作时动作会互相覆盖或无法完整保留
这一点对模式 3 是直接阻塞对模式 2 也不够健壮
### 3.3 切宠和当前上场位逻辑仍大量默认使用 `CurPet[0]`
虽然 `CurPet` 已经是数组但主流程中不少逻辑仍固定操作 `0` 号位
典型影响
- 死亡换宠
- 主动换宠
- 当前出手单位检查
- 当前目标单位检查
这部分需要按 `actorIndex` 或上场槽位改造
### 3.4 开战协议仍然只有两个当前单位
当前开战下发协议仍然是双单位结构
关键位置
- `logic/service/fight/info/info.go`
- `FightStartOutboundInfo`
- 仍只有 `Info1` `Info2`
这不适合多上场位模式
### 3.5 公共接口仍是旧的单单位接口
关键位置
- `logic/service/common/fight.go`
- `FightI`
目前接口仍只有
- `UseSkill(c, id)`
- `ChangePet(c, id)`
- `UseItem(c, cacthid, itemid)`
indexed 版本只存在于具体实现 `FightC` 没有进入正式接口层
---
## 4. 当前实现与目标模式的对应关系
### 4.1 模式 1
`1玩家:N精灵:1上场 VS 1玩家:N精灵:1上场`
当前支持
原因
- 当前默认就是操作 `0` 号单位
- 当前默认就是攻击 `0` 号目标
- 当前回合系统仍是每边 1 个动作这与模式 1 一致
### 4.2 模式 2
`N玩家:N精灵:N上场 VS N玩家:N精灵:N上场`
当前不支持
直接原因
- 一回合只收 2 个动作
- 一回合只结算 2 个动作
- 协议仍只同步 2 个当前上场位
### 4.3 模式 3
`1玩家:N精灵:N上场 VS 1玩家:N精灵:N上场`
当前不支持
直接原因
- 同一玩家的多个动作无法作为同回合动作列表完整保留
- 主流程仍不是按动作列表统一排序和执行
---
## 5. 需要完成的工作
### 5.1 改造动作收集模型
将当前每边 1 个动作的模型改成每个可操作上场位 1 个动作的模型
至少需要做到
- 同一玩家可以在同一回合提交多个动作
- 每个动作能区分是哪个上场位发出的
- 每个动作能区分目标上场位
建议将动作唯一键至少扩为
- `playerID`
- `actorIndex`
### 5.2 改造回合结算模型
将当前
- `resolveRound(p1Action, p2Action)`
改成
- `resolveRound(actions []action.BattleActionI)`
并完成
- 动作列表排序
- 按优先级速度等规则统一排序
- 排序后逐个结算
注意
- 当前 effect/node 体系里仍有大量双动作接口不适合一次性全部重写
- 建议先在主流程做兼容层逐步过渡
### 5.3 按槽位处理切宠与死亡换宠
将当前固定 `CurPet[0]` 的逻辑改成按槽位处理
- 主动换宠
- 被动死亡换宠
- 死亡校验
- 出手资格判断
### 5.4 增加开战与战斗同步结构
将当前的双单位同步结构扩成可支持多上场位的结构但是保持协议结构不变现在是固定两个可以改成数组来实现
重点是
- 开战协议
- 当前上场位同步
- 切宠同步
- 可能的回合播报结构
### 5.5 补齐公共接口
indexed 版本能力补进接口层避免只能通过具体实现类型访问
建议新增类似接口
- `UseSkillAt(...)`
- `ChangePetAt(...)`
- `UseItemAt(...)`
---
## 6. 推荐实施顺序
建议按下面顺序推进避免一次性改动面过大
1. 先改动作队列和动作收集逻辑
2. 再改回合结算为动作列表
3. 再改切宠和死亡换宠按槽位处理
4. 最后改协议和正式接口
不建议一开始就全量重写 effect/node 接口因为当前大量效果实现仍假设双动作上下文
---
## 7. 建议重点查看文件
- `logic/service/fight/action.go`
- `logic/service/fight/loop.go`
- `logic/service/fight/fightc.go`
- `logic/service/fight/input.go`
- `logic/service/fight/input/input.go`
- `logic/service/fight/action/BattleAction.go`
- `logic/service/fight/info/info.go`
- `logic/service/common/fight.go`
---
## 8. 完成标准
至少满足以下条件才算这次改造完成
1. 同一玩家可以在同一回合给多个上场位分别提交动作动作不会互相覆盖
2. 双方多个上场位可以在同一回合统一排序并依次结算
3. 攻击目标位可选不再默认只能打对面 `0`
4. 切宠可以按上场槽位处理
5. 模式 1 不回归
6. 代码编译通过
---
## 9. 最低验证要求
至少执行
- `cd /workspace/logic && go build ./...`
- `cd /workspace/logic && go test ./service/fight/effect`
如果本轮改动较大建议再补一轮
- `cd /workspace/logic && go test ./...`
---
## 10. 额外提醒
- 当前仓库工作区可能是脏的不要回滚无关修改
- 这次改造的真正核心不是结构字段改数组而是把回合系统从双动作模型改成动作列表模型
- 已有 `ActorIndex/TargetIndex` 只是入口铺垫不代表多单位模式已经完成

View File

@@ -0,0 +1,224 @@
# PVP Match Via RPC, Battle Via Redis
## 目标
本次调整先不解决 `login` 更新期间的排队保活和补偿问题只收敛到一个更简单可控的方案
- 匹配请求走 `logic -> login` 的同步 RPC
- 对战过程仍走 `logic` 本地战斗 + Redis 转发战斗指令
- `login` 不可用时`logic` 直接返回匹配服务不可用
- 前端通过轮询重新发起 / 更新匹配请求不在后端保留离线补偿队列
这个方案的核心是先把能否立即判断匹配服务可用做好不继续依赖 Redis PubSub 做匹配入口
## 当前现状
### 现有匹配入口
- 前端 `2458` 进入 [logic/controller/fight_巅峰.go](/workspace/logic/controller/fight_巅峰.go#L19)
- 当前 `JoINtop` 直接调用 [logic/service/fight/pvp/service.go](/workspace/logic/service/fight/pvp/service.go#L83) `JoinPeakQueue`
- `JoinPeakQueue` 当前实现是本地建 `localQueueTicket`并通过 Redis `publish` `queue_join`
### 现有跨服协调
- `logic` 侧订阅 PVP Redis topic 的入口在 [common/rpc/func.go](/workspace/common/rpc/func.go#L153)
- PVP 匹配状态当前存在 `logic/service/fight/pvp/service.go` manager 内存里
- `queues`
- `lastSeen`
- `localQueues`
- `sessions`
- `userSession`
### 现有 RPC 能力
- `logic` 启动时通过 [common/rpc/rpc.go](/workspace/common/rpc/rpc.go#L113) 建立到 `login` RPC client
- `login` `/rpc/*` 入口绑定在 [modules/base/middleware/middleware.go](/workspace/modules/base/middleware/middleware.go#L152)
- `login` RPC server [common/rpc/rpc.go](/workspace/common/rpc/rpc.go#L101) 暴露
### 当前问题
Redis PubSub 适合广播消息不适合同步判断服务是否可用
如果继续让匹配入口走 PubSub
- `logic` 无法在请求当下知道 `login` 是否真能处理
- `login` 更新重启未订阅时匹配请求可能直接丢失
- 前端即使轮询也只是重复投递不能精确表达当前匹配服务可用/不可用
## 收敛后的职责划分
### login
`login` 只负责匹配控制面
- 接收 `logic` 发来的同步匹配 RPC
- 判断当前匹配服务是否可用
- 维护匹配队列
- 找到对手后记录 match 结果
- 再通过 Redis 或其他异步方式通知对应 `logic` 开始 Ban/Pick / Battle
### logic
`logic` 只负责
- 接收前端匹配请求
- 同步 RPC `login`
- RPC 失败时立即返回匹配服务不可用
- RPC 成功时返回排队中
- 收到 match 结果后负责真正 `fight.NewFight(...)`
- 对战期间继续使用现有 Redis topic 转发战斗指令
### Redis
Redis 只保留在对战消息面
- `match_found`
- `ban_pick_submit`
- `battle_command`
- `packet_relay`
- `session_close`
也就是说
- 匹配入口走 RPC
- 对战过程走 Redis
## 推荐目标链路
### 1. 前端加入/更新匹配
前端定期轮询 `logic` 的加入/更新接口
`logic` 处理流程
1. 校验玩家当前战斗状态
2. 同步调用 `login` 的匹配 RPC
3. 如果 RPC 成功返回排队中
4. 如果 RPC 失败清理本地匹配状态返回匹配服务不可用
### 2. login 完成匹配
`login` 维护排队队列和匹配结果匹配成功后
1. 确定 host / guest 所在 `logic`
2. 通过 Redis 通知两个 `logic`
3. host `logic` 开战
4. guest `logic` 设置远端代理并进入 Ban/Pick 或战斗态
### 3. 对战期间
继续复用当前 `logic/service/fight/pvp/service.go` 内的 Redis 指令转发模式
- 战斗操作通过 Redis topic 转发
- host `logic` 维持真实战斗对象
- guest `logic` 维持 remote proxy
## 失败语义
本阶段不做补偿不做离线保队列
### login 不在线
如果 `logic -> login` RPC 调用失败
- 本次匹配直接失败
- `logic` 清理本地匹配状态
- 返回前端匹配服务不可用
### 前端轮询停止
如果前端不再轮询
- 视为用户不再持续请求匹配
- `logic` 不负责继续保活
- 是否从 `login` 队列移除 `login` 的超时策略决定
### login 更新中
如果 `login` 正在更新
- `logic` 的同步 RPC 会失败
- 前端当前轮询会收到匹配服务不可用
- `login` 恢复后前端下一轮再发起匹配
这是本阶段明确接受的行为不在后端做补偿
## 最小实现建议
### 先增加 RPC 健康/匹配接口
[common/rpc/rpc.go](/workspace/common/rpc/rpc.go) 增加面向 `logic -> login` RPC 方法
建议最小接口
- `MatchJoinOrUpdate(PVPMatchJoinPayload) error`
- `MatchCancel(userID) error`
如果需要单独健康检查也可以加
- `MatchPing() error`
但在最小方案里`MatchJoinOrUpdate` 自身就可以承担健康检查职责
### logic 的匹配入口改为同步 RPC
改造 [logic/controller/fight_巅峰.go](/workspace/logic/controller/fight_巅峰.go#L19) [logic/service/fight/pvp/service.go](/workspace/logic/service/fight/pvp/service.go#L83)
- 入口不再直接发布 `queue_join`
- 先发 RPC `login`
- 成功才更新本地匹配状态
- 失败直接返回错误
- 取消匹配时通过 `MatchCancel` best-effort 清理
### 保留 Redis 对战链路
[logic/service/fight/pvp/service.go](/workspace/logic/service/fight/pvp/service.go#L170) 之后的 Redis 消费match result 处理Ban/Pick战斗 relay 不需要一次性重写可以继续保留
调整重点是
- 不再让匹配入口依赖 PubSub
- 让对战过程继续走 Redis
## 对前端的要求
前端不要无脑重复 join而是按轮询更新匹配状态处理
建议行为
1. 首次点击匹配时发一次加入
2. 匹配中每隔 `3~5s` 轮询一次更新
3. 如果返回匹配服务不可用前端退出匹配态并提示
4. 如果返回已匹配/进入 Ban/Pick前端切换到对应界面
## 本阶段不做的事
以下内容明确不在这次最小改造内
- `login` 更新期间的排队保活
- 持久化消息补偿
- `login` 重启后的队列恢复
- Redis Stream
- `login` 实例协调
- 匹配服务自动拉起目标 `logic`
## 后续可选增强
如果后面要继续提高可用性可以再逐步演进为
1. 匹配入口仍走 RPC
2. `login` 内部把队列落 Redis
3. 加入 ticket 和续租机制
4. login 更新时支持恢复匹配状态
但这不是当前阶段的目标
## 最终收敛结论
当前阶段建议明确成一句话
`匹配走 RPC对战走 Redis。`
对应业务语义
- 需要立即判断服务可用性的时候 RPC
- 需要跨服转发战斗消息的时候 Redis

View File

@@ -1,4 +1,4 @@
go 1.25.0
go 1.25
use (
./common
@@ -20,6 +20,7 @@ use (
./common/utils/sturc
./common/utils/timer
./common/utils/xml
./common/utils/zset
./logic
./login
./modules

View File

@@ -1,99 +0,0 @@
# 屎山代码分析报告
## 总体评估
- **质量评分**: 31.03/100
- **质量等级**: 🌸 偶有异味 - 基本没事但是有伤风化
- **分析文件数**: 203
- **代码总行数**: 20972
## 质量指标
| 指标 | 得分 | 权重 | 状态 |
|------|------|------|------|
| 状态管理 | 4.84 | 0.15 | |
| 循环复杂度 | 6.28 | 0.25 | |
| 命名规范 | 25.00 | 0.10 | |
| 错误处理 | 35.00 | 0.15 | |
| 代码结构 | 45.00 | 0.20 | |
| 代码重复度 | 55.00 | 0.15 | |
| 注释覆盖率 | 55.94 | 0.15 | |
## 问题文件 (Top 5)
### 1. /workspace/blazing/common/utils/sturc/field.go (得分: 53.85)
**问题分类**: 🔄 复杂度问题:10, 📝 注释问题:1, 其他问题:5
**主要问题**:
- 函数 Size 的循环复杂度较高 (12)建议简化
- 函数 packVal 的循环复杂度过高 (23)考虑重构
- 函数 Pack 的循环复杂度较高 (14)建议简化
- 函数 unpackVal 的循环复杂度过高 (21)考虑重构
- 函数 Unpack 的循环复杂度较高 (12)建议简化
- 函数 'Size' () 较长 (33 )可考虑重构
- 函数 'Size' () 复杂度过高 (12)建议简化
- 函数 'packVal' () 过长 (69 )建议拆分
- 函数 'packVal' () 复杂度严重过高 (23)必须简化
- 函数 'Pack' () 较长 (48 )可考虑重构
- 函数 'Pack' () 复杂度过高 (14)建议简化
- 函数 'unpackVal' () 过长 (57 )建议拆分
- 函数 'unpackVal' () 复杂度严重过高 (21)必须简化
- 函数 'Unpack' () 较长 (33 )可考虑重构
- 函数 'Unpack' () 复杂度过高 (12)建议简化
- 代码注释率极低 (1.38%)几乎没有注释
### 2. /workspace/blazing/common/utils/sturc/fields.go (得分: 46.83)
**问题分类**: 🔄 复杂度问题:4, 📝 注释问题:1, 其他问题:2
**主要问题**:
- 函数 Pack 的循环复杂度较高 (12)建议简化
- 函数 Unpack 的循环复杂度过高 (21)考虑重构
- 函数 'Pack' () 较长 (42 )可考虑重构
- 函数 'Pack' () 复杂度过高 (12)建议简化
- 函数 'Unpack' () 过长 (73 )建议拆分
- 函数 'Unpack' () 复杂度严重过高 (21)必须简化
- 代码注释率极低 (3.91%)几乎没有注释
### 3. /workspace/blazing/common/utils/sturc/parse.go (得分: 46.68)
**问题分类**: 🔄 复杂度问题:4, 📝 注释问题:1, 其他问题:3
**主要问题**:
- 代码注释率较低 (6.93%)建议增加注释
- 函数 parseField 的循环复杂度较高 (13)建议简化
- 函数 parseFieldsLocked 的循环复杂度过高 (18)考虑重构
- 函数 'parseField' () 过长 (64 )建议拆分
- 函数 'parseField' () 复杂度过高 (13)建议简化
- 函数 'parseFieldsLocked' () 过长 (64 )建议拆分
- 函数 'parseFieldsLocked' () 复杂度严重过高 (18)必须简化
- 函数 'parseFields' () 较长 (31 )可考虑重构
### 4. /workspace/blazing/common/utils/xml/typeinfo.go (得分: 46.13)
**问题分类**: 🔄 复杂度问题:6, 其他问题:3
**主要问题**:
- 函数 getTypeInfo 的循环复杂度过高 (18)考虑重构
- 函数 structFieldInfo 的循环复杂度过高 (33)考虑重构
- 函数 addFieldInfo 的循环复杂度过高 (20)考虑重构
- 函数 'getTypeInfo' () 过长 (58 )建议拆分
- 函数 'getTypeInfo' () 复杂度严重过高 (18)必须简化
- 函数 'structFieldInfo' () 极度过长 (114 )必须拆分
- 函数 'structFieldInfo' () 复杂度严重过高 (33)必须简化
- 函数 'addFieldInfo' () 过长 (66 )建议拆分
- 函数 'addFieldInfo' () 复杂度严重过高 (20)必须简化
### 5. /workspace/blazing/common/utils/go-jsonrpc/auth/handler.go (得分: 45.61)
**问题分类**: 📝 注释问题:1, 其他问题:1
**主要问题**:
- 函数 'ServeHTTP' () 较长 (31 )可考虑重构
- 代码注释率极低 (0.00%)几乎没有注释
## 改进建议
### 高优先级
- 继续保持当前的代码质量标准
### 中优先级
- 可以考虑进一步优化性能和可读性
- 完善文档和注释便于团队协作

58
help/三主宠查询.sql Normal file
View File

@@ -0,0 +1,58 @@
-- 删除每个多余精灵组中除了最早创建的其余记录
WITH pet_group_mapping AS (
SELECT
id,
player_id,
-- 核心修正PARTITION BY中直接写分组逻辑
ROW_NUMBER() OVER (
PARTITION BY
player_id,
CASE -- 3个一组的分组逻辑
WHEN (data->>'ID')::INT BETWEEN 1 AND 3 THEN 'group_1_3'
WHEN (data->>'ID')::INT BETWEEN 4 AND 6 THEN 'group_4_6'
WHEN (data->>'ID')::INT BETWEEN 7 AND 9 THEN 'group_7_9'
WHEN (data->>'ID')::INT BETWEEN 301 AND 303 THEN 'group_301_303'
WHEN (data->>'ID')::INT BETWEEN 304 AND 306 THEN 'group_304_306'
WHEN (data->>'ID')::INT BETWEEN 307 AND 309 THEN 'group_307_309'
END
ORDER BY "createTime" ASC
) AS rn,
-- 定义pet_group用于筛选多余组
CASE
WHEN (data->>'ID')::INT BETWEEN 1 AND 3 THEN 'group_1_3'
WHEN (data->>'ID')::INT BETWEEN 4 AND 6 THEN 'group_4_6'
WHEN (data->>'ID')::INT BETWEEN 7 AND 9 THEN 'group_7_9'
WHEN (data->>'ID')::INT BETWEEN 301 AND 303 THEN 'group_301_303'
WHEN (data->>'ID')::INT BETWEEN 304 AND 306 THEN 'group_304_306'
WHEN (data->>'ID')::INT BETWEEN 307 AND 309 THEN 'group_307_309'
END AS pet_group
FROM "player_pet"
WHERE deleted_at IS NULL
),
excess_groups AS (
SELECT player_id, pet_group
FROM pet_group_mapping
WHERE pet_group IS NOT NULL
GROUP BY player_id, pet_group
HAVING COUNT(*) > 1
)
DELETE FROM "player_pet"
WHERE id IN (
SELECT pgm.id
FROM pet_group_mapping pgm
INNER JOIN excess_groups eg ON pgm.player_id = eg.player_id AND pgm.pet_group = eg.pet_group
WHERE pgm.rn > 1
);
//删除多余的异常融合精灵
DELETE FROM "player_pet" pp
WHERE
pp.deleted_at IS NULL
AND pp.is_vip = 0
AND (pp.data->>'OldCatchTime')::BIGINT != 0
AND NOT EXISTS (
SELECT 1
FROM config_fusion_pet cfp
WHERE (pp.data->>'ID')::INT BETWEEN cfp.result_pet_id AND cfp.result_pet_id + 2
);

View File

@@ -1 +1,49 @@
select setval('base_sys_user_id_seq', 10000000, false);
select setval('base_sys_user_id_seq', 10000000, false);
-- 清理旧函数
ALTER TABLE base_sys_user
ALTER COLUMN id SET DEFAULT nextval('base_sys_user_id_seq');
DROP FUNCTION IF EXISTS shuffle_8digit() CASCADE;
-- 8位自增 纯数字位置互换 依旧8位 100%不重复
CREATE OR REPLACE FUNCTION shuffle_8digit()
RETURNS bigint
LANGUAGE plpgsql
VOLATILE
AS $$
DECLARE
seq bigint;
d0 int; d1 int; d2 int; d3 int;
d4 int; d5 int; d6 int; d7 int;
BEGIN
seq := nextval('base_sys_user_id_seq'); -- 原本就是8位
-- 8 位数字拆出来d7 d6 d5 d4 d3 d2 d1 d0
d7 := (seq / 10000000) % 10;
d6 := (seq / 1000000) % 10;
d5 := (seq / 100000) % 10;
d4 := (seq / 10000) % 10;
d3 := (seq / 1000) % 10;
d2 := (seq / 100) % 10;
d1 := (seq / 10) % 10;
d0 := seq % 10;
-- 固定位置互换一对一置换绝对不重复
RETURN
d3*10000000 +
d7*1000000 +
d1*100000 +
d5*10000 +
d2*1000 +
d6*100 +
d0*10 +
d4;
END;
$$;
-- 启用
ALTER TABLE base_sys_user
ALTER COLUMN id SET DEFAULT shuffle_8digit();

View File

@@ -0,0 +1,16 @@
SELECT
t1.*
FROM player_item t1
JOIN (
SELECT
player_id,
item_id
FROM player_item
WHERE is_vip = 0
GROUP BY player_id, item_id
HAVING COUNT(*) > 1
) t2
ON t1.player_id = t2.player_id
AND t1.item_id = t2.item_id
WHERE t1.is_vip = 0
ORDER BY t1.player_id, t1.item_id, t1."createTime";

View File

@@ -0,0 +1,71 @@
-- 验证查询宠物ID为273/274玩家拥有2条且非最早创建的记录只查不删
WITH player_pet_ranked AS (
SELECT
id, -- 记录主键ID
player_id,
(data->>'ID')::INT AS pet_id, -- 便于核对宠物ID
"createTime",
ROW_NUMBER() OVER (
PARTITION BY player_id
ORDER BY "createTime" ASC
) AS rn
FROM "player_pet"
WHERE (data->>'ID')::INT IN (273, 274) -- 改为273或274
)
SELECT *
FROM player_pet_ranked
WHERE rn > 1 -- rn>1是要删除的记录
AND player_id IN (
SELECT player_id
FROM player_pet_ranked
GROUP BY player_id
HAVING COUNT(*) >= 2
);
-- 核心删除保留每个玩家下273/274宠物中createTime最早的那条删除其余
WITH player_pet_ranked AS (
SELECT
id, -- 必须取主键ID用于精准删除
player_id,
ROW_NUMBER() OVER (
PARTITION BY player_id
ORDER BY "createTime" ASC
) AS rn
FROM "player_pet"
WHERE (data->>'ID')::INT IN (273, 274) -- 关键修改70273,274
)
DELETE FROM "player_pet"
WHERE id IN (
SELECT id
FROM player_pet_ranked
WHERE rn > 1 -- 删除非最早的记录
AND player_id IN (
SELECT player_id
FROM player_pet_ranked
GROUP BY player_id
HAVING COUNT(*) >= 2 -- 仅处理拥有2条273/274宠物的玩家
)
);
-- 查询player_id=136144宠物ID=303的所有未删除记录
SELECT
id, -- 记录主键ID
player_id,
(data->>'ID')::INT AS pet_id, -- 宠物ID便于核对
"createTime", -- 创建时间
deleted_at, -- 软删除字段确认是否未删除
data -- 完整的宠物数据可选如需查看全部信息
FROM "player_pet"
WHERE
player_id = 136144 -- 精准定位玩家ID=136144
AND (data->>'ID')::INT = 301 -- 精准定位宠物ID=303
AND deleted_at IS NULL; -- 仅查未被软删除的记录如需包含删除的可删除此条件

View File

@@ -0,0 +1,8 @@
-- 所有宠物ID + 对应记录总数去重前
SELECT
(data->>'ID')::INT AS pet_id, -- 宠物ID
COUNT(*) AS total_records -- 该宠物的总持有记录数
FROM "player_pet"
WHERE data->>'ID' IS NOT NULL -- 过滤无宠物ID的无效记录
GROUP BY (data->>'ID')::INT
ORDER BY total_records DESC, pet_id ASC; -- 按数量降序ID升序排列

42
help/约束类.sql Normal file
View File

@@ -0,0 +1,42 @@
-- 玩家+物品+VIP状态 联合唯一
ALTER TABLE player_item
ADD CONSTRAINT uk_player_item_player_item_vip
UNIQUE (player_id, item_id, is_vip);
-- 玩家+挖矿 联合唯一
CREATE UNIQUE INDEX uk_talk_player ON player_talk (talk_id, player_id);
-- 玩家+任务 联合唯一
CREATE UNIQUE INDEX uk_player_task ON player_task (player_id, task_id);
-- 玩家+称号 联合唯一
CREATE UNIQUE INDEX uk_player_title ON player_title (player_id, is_vip) WHERE deleted_at IS NULL;
-- 玩家+精灵 联合唯一
CREATE UNIQUE INDEX uk_player_pet ON player_pet (player_id, is_vip, catch_time) WHERE deleted_at IS NULL;
-- 玩家+CDK 联合唯一
CREATE UNIQUE INDEX uk_player_cdk_log
ON player_cdk_log (player_id, code_id, is_vip)
WHERE deleted_at IS NULL;
-- 玩家孵蛋 联合唯一
CREATE UNIQUE INDEX uk_player_egg
ON player_egg (player_id, is_vip)
WHERE deleted_at IS NULL;
---PVP索引
CREATE UNIQUE INDEX uk_player_pvp
ON player_pvp (player_id, season)
WHERE deleted_at IS NULL;
--签到
CREATE UNIQUE INDEX uk_player_sign_in_log
ON player_sign_in_log (player_id, sign_in_id, is_vip)
WHERE deleted_at IS NULL;
--房间索引
CREATE UNIQUE INDEX uk_player_room_house
ON player_room_house (player_id, is_vip)
WHERE deleted_at IS NULL;

18
help/诊断较慢.sql Normal file
View File

@@ -0,0 +1,18 @@
-- 查看当前活跃的 SQL排查慢查询
SELECT pid, now() - query_start AS duration, query
FROM pg_stat_activity
WHERE state = 'active' AND now() - query_start > '5 seconds'::interval;
-- 查看表的访问统计找出热点表
SELECT relname, seq_scan, idx_scan, n_live_tup
FROM pg_stat_user_tables
ORDER BY seq_scan DESC LIMIT 10;
-- 查看索引使用情况找出未使用的索引
SELECT relname AS table_name, indexrelname AS index_name, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0;
-- 更新统计信息当执行计划不准时
ANALYZE users; -- 单表更新
ANALYZE; -- 全库更新

59
help/随机id生成 Normal file
View File

@@ -0,0 +1,59 @@
-- 清理旧的
ALTER TABLE base_sys_user
ALTER COLUMN id SET DEFAULT nextval('base_sys_user_id_seq');
DROP FUNCTION IF EXISTS shuffle_9digit() CASCADE;
-- 9位乱序首尾互换 + 中间全部打乱
-- 纯数字换位100% 不重复速度极快
CREATE OR REPLACE FUNCTION shuffle_9digit()
RETURNS bigint
LANGUAGE plpgsql
VOLATILE
AS $$
DECLARE
seq bigint;
d0 int; -- 原最后一位
d1 int;
d2 int;
d3 int;
d4 int;
d5 int;
d6 int;
d7 int; -- 原第一位
BEGIN
seq := nextval('base_sys_user_id_seq'); -- 8位自增
-- 拆分 8 d7 d6 d5 d4 d3 d2 d1 d0
d7 := (seq / 10000000) % 10;
d6 := (seq / 1000000) % 10;
d5 := (seq / 100000) % 10;
d4 := (seq / 10000) % 10;
d3 := (seq / 1000) % 10;
d2 := (seq / 100) % 10;
d1 := (seq / 10) % 10;
d0 := seq % 10;
-- 构造成 9 规则
-- 1. 首尾互换原最后一位放第1位原第1位放最后
-- 2. 中间全部打乱位置
RETURN
d0 * 100000000 + -- 原最后一位 第1位
d3 * 10000000 +
d1 * 1000000 +
d5 * 100000 +
d2 * 10000 +
d6 * 1000 +
d4 * 100 +
d7 * 10 +
d0; -- 原第一位 第9位
END;
$$;
-- 启用
ALTER TABLE base_sys_user
ALTER COLUMN id SET DEFAULT shuffle_9digit();
ALTER TABLE base_sys_user
ALTER COLUMN id
SET DEFAULT nextval('base_sys_user_id_seq');

View File

@@ -4,30 +4,36 @@
package controller
import (
"blazing/common/rpc"
"blazing/cool"
"blazing/logic/service/common"
"fmt"
"strconv"
"strings"
"bytes"
"context"
"fmt"
"reflect"
"strconv"
"strings"
"sync"
"github.com/gogf/gf/v2/os/glog"
"github.com/lunixbochs/struc"
"github.com/panjf2000/gnet/v2"
)
// Maincontroller 是控制器层共享变量。
var Maincontroller = &Controller{} //注入service
// Controller 分发cmd逻辑实现
type Controller struct {
Port uint16
RPCClient struct {
UID uint32
RPCClient *struct {
Kick func(uint32) error
RegisterLogic func(uint16, uint16) error
RegisterLogic func(uint32, uint32) error
MatchJoinOrUpdate func(rpc.PVPMatchJoinPayload) error
MatchCancel func(uint32) error
}
}
@@ -38,159 +44,198 @@ type Controller struct {
func ParseCmd[T any](data []byte) T {
var result T
// 使用struc.Unpack将字节数据解包到result变量中
struc.Unpack(bytes.NewBuffer(data), &result)
struc.Unpack(bytes.NewReader(data), &result)
return result
}
// Init 初始化控制器注册所有cmd处理方法
// 参数 isGame: 标识是否为游戏服务器(true)或登录服务器(false)
func Init(isGame bool) {
// 获取控制器实例的反射值
controllerValue := reflect.ValueOf(Maincontroller)
// 获取控制器类型
controllerType := controllerValue.Type()
// 遍历控制器的所有方法
for i := 0; i < controllerType.NumMethod(); i++ {
method := controllerType.Method(i)
methodValue := controllerValue.MethodByName(method.Name)
methodValue := controllerValue.Method(i)
methodType := methodValue.Type()
// 获取方法第一个参数的类型(请求结构体)
if methodValue.Type().NumIn() == 0 {
if methodType.NumIn() == 0 {
continue
}
// 解析请求结构体中的cmd标签
for _, cmd := range getCmd(methodValue.Type().In(0)) {
if cmd == 0 { // 说明不是有效的注册方法
reqArgType := methodType.In(0)
if reqArgType.Kind() != reflect.Ptr || reqArgType.Elem().Kind() != reflect.Struct {
glog.Warning(context.Background(), "方法首参必须为结构体指针", method.Name, "跳过注册")
continue
}
reqType := reqArgType.Elem()
binding := getCmdBinding(reqType)
for _, cmd := range binding.cmds {
if cmd == 0 {
glog.Warning(context.Background(), "方法参数必须包含CMD参数", method.Name, "跳过注册")
continue
}
// 根据服务器类型过滤cmd
// 登录服务器只处理小于1000的cmd
if methodType.NumIn() != 2 {
glog.Warning(context.Background(), "方法参数数量必须为2", method.Name, "跳过注册")
continue
}
if !isGame && cmd > 1000 {
continue
}
// 游戏服务器只处理大于等于1000的cmd
if isGame && cmd < 1000 {
continue
}
// 注册命令处理函数
if cool.Config.ServerInfo.IsDebug != 0 {
fmt.Println("注册方法", cmd, method.Name)
}
reqTypeForNew := reqType
cmdInfo := cool.Cmd{
Func: methodValue,
Req: methodValue.Type().In(0).Elem(),
// Res: , // TODO 待实现对不同用户初始化方法以取消全局cmdcache
Func: methodValue,
Req: reqType,
HeaderFieldIndex: append([]int(nil), binding.headerFieldIndex...),
UseConn: methodType.In(1) == connType,
NewReqFunc: func() interface{} {
return reflect.New(reqTypeForNew).Interface()
},
NewReqValue: func() reflect.Value {
return reflect.New(reqTypeForNew)
},
}
if _, exists := cool.CmdCache[cmd]; exists {
panic(fmt.Sprintf("命令处理方法已存在,跳过注册 %d %s", cmd, method.Name))
}
cool.CmdCache[cmd] = cmdInfo
// if exists { // 方法已存在
// glog.Error(context.Background(), "命令处理方法已存在,跳过注册", cmd, method.Name)
// }
}
}
}
var targetType = reflect.TypeOf(common.TomeeHeader{})
var (
targetType = reflect.TypeOf(common.TomeeHeader{})
connType = reflect.TypeOf((*gnet.Conn)(nil)).Elem()
cmdTypeCache sync.Map
)
// 默认返回值(无匹配字段/解析失败时)
const defaultCmdValue = 0
// getCmd 从结构体类型中提取绑定的cmd指令递归查找嵌套结构体支持值/指针类型的TomeeHeader
// 参数 typ: 待解析的结构体类型(支持多层指针)
// 返回值: 解析到的cmd切片无匹配/解析失败时返回[defaultCmdValue]
func getCmd(typ reflect.Type) []uint32 {
// 递归解引用所有指针类型(处理 *struct、**struct 等场景)
type cmdBinding struct {
cmds []uint32
headerFieldIndex []int
}
func normalizeStructType(typ reflect.Type) reflect.Type {
for typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
return typ
}
// 非结构体类型直接返回默认值
if typ.Kind() != reflect.Struct {
return []uint32{defaultCmdValue}
// getCmdBinding 从结构体类型中提取绑定的cmd指令和头字段位置。
func getCmdBinding(typ reflect.Type) cmdBinding {
typ = normalizeStructType(typ)
if cached, ok := cmdTypeCache.Load(typ); ok {
return cached.(cmdBinding)
}
// 遍历结构体字段查找TomeeHeader字段并解析cmd
if typ.Kind() != reflect.Struct {
binding := cmdBinding{cmds: []uint32{defaultCmdValue}}
cmdTypeCache.Store(typ, binding)
return binding
}
if binding, ok := findCmdBinding(typ, make(map[reflect.Type]struct{})); ok {
cmdTypeCache.Store(typ, binding)
return binding
}
binding := cmdBinding{cmds: []uint32{defaultCmdValue}}
cmdTypeCache.Store(typ, binding)
return binding
}
func findCmdBinding(typ reflect.Type, visiting map[reflect.Type]struct{}) (cmdBinding, bool) {
typ = normalizeStructType(typ)
if typ.Kind() != reflect.Struct {
return cmdBinding{}, false
}
if _, seen := visiting[typ]; seen {
return cmdBinding{}, false
}
visiting[typ] = struct{}{}
defer delete(visiting, typ)
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
// 尝试解析当前字段的cmd标签
cmdSlice, err := parseCmdTagWithStructField(field)
if err == nil { // 解析成功,直接返回结果
return cmdSlice
cmdSlice, isHeader, err := parseCmdTagWithStructField(field)
if isHeader && err == nil {
return cmdBinding{
cmds: cmdSlice,
headerFieldIndex: append([]int(nil), field.Index...),
}, true
}
// 递归处理嵌套结构体(值/指针类型)
nestedTyp := field.Type
if nestedTyp.Kind() == reflect.Ptr {
nestedTyp = nestedTyp.Elem()
nestedTyp := normalizeStructType(field.Type)
if nestedTyp.Kind() != reflect.Struct {
continue
}
if nestedTyp.Kind() == reflect.Struct {
// 递归查找找到有效cmd则立即返回
if nestedCmd := getCmd(nestedTyp); len(nestedCmd) > 0 && nestedCmd[0] != defaultCmdValue {
return nestedCmd
}
nestedBinding, ok := findCmdBinding(nestedTyp, visiting)
if !ok {
continue
}
fieldIndex := make([]int, 0, len(field.Index)+len(nestedBinding.headerFieldIndex))
fieldIndex = append(fieldIndex, field.Index...)
fieldIndex = append(fieldIndex, nestedBinding.headerFieldIndex...)
nestedBinding.headerFieldIndex = fieldIndex
return nestedBinding, true
}
// 未找到目标字段/所有解析失败,返回默认值
return []uint32{defaultCmdValue}
return cmdBinding{}, false
}
// parseCmdTagWithStructField 校验字段是否为TomeeHeader值/指针并解析cmd标签
// 参数 field: 结构体字段元信息
// 返回值: 解析后的cmd切片目标类型/解析失败返回错误
func parseCmdTagWithStructField(field reflect.StructField) ([]uint32, error) {
// 判断字段类型是否为 TomeeHeader 或 *TomeeHeader
var isTomeeHeader bool
switch {
case field.Type == targetType: // 值类型
isTomeeHeader = true
case field.Type.Kind() == reflect.Ptr && field.Type.Elem() == targetType: // 指针类型
isTomeeHeader = true
default:
isTomeeHeader = false
// 返回值: 解析后的cmd切片是否为目标类型解析失败错误
func parseCmdTagWithStructField(field reflect.StructField) ([]uint32, bool, error) {
if field.Type != targetType && !(field.Type.Kind() == reflect.Ptr && field.Type.Elem() == targetType) {
return nil, false, nil
}
// 非目标类型返回错误
if !isTomeeHeader {
return nil, fmt.Errorf("field %s (type: %v) is not common.TomeeHeader or *common.TomeeHeader",
field.Name, field.Type)
}
// 提取cmd标签
cmdStr := field.Tag.Get("cmd")
if cmdStr == "" {
return nil, fmt.Errorf("field %s cmd tag is empty", field.Name)
return nil, true, fmt.Errorf("field %s cmd tag is empty", field.Name)
}
// 高性能解析标签为uint32切片替代gconv减少第三方依赖且可控
parts := strings.Split(cmdStr, "|")
result := make([]uint32, 0, len(parts))
for idx, s := range parts {
// 去除空白字符(兼容标签中意外的空格)
s = strings.TrimSpace(s)
result := make([]uint32, 0, strings.Count(cmdStr, "|")+1)
remain := cmdStr
for idx := 0; ; idx++ {
part, next, found := strings.Cut(remain, "|")
s := strings.TrimSpace(part)
if s == "" {
return nil, fmt.Errorf("field %s cmd tag part %d is empty", field.Name, idx)
return nil, true, fmt.Errorf("field %s cmd tag part %d is empty", field.Name, idx)
}
// 手动解析uint32比gconv更可控避免隐式转换问题
num, err := strconv.ParseUint(s, 10, 32)
if err != nil {
return nil, fmt.Errorf("field %s cmd tag part %d parse error: %v (value: %s)",
return nil, true, fmt.Errorf("field %s cmd tag part %d parse error: %v (value: %s)",
field.Name, idx, err, s)
}
result = append(result, uint32(num))
if !found {
break
}
remain = next
}
return result, nil
return result, true, nil
}

View File

@@ -0,0 +1,177 @@
package controller
import (
"blazing/common/data"
"blazing/common/socket/errorcode"
"blazing/logic/service/common"
"blazing/logic/service/player"
"blazing/logic/service/task"
"blazing/modules/player/model"
"github.com/pointernil/bitset32"
)
const (
masterCupTaskID uint32 = 111
masterCupRewardItemMin uint32 = 80000000
masterCupRewardItemMax uint32 = 80000015
)
var masterCupRewardElementOrder = [...]uint32{1, 2, 3, 5, 11, 4, 6, 7, 9}
var masterCupRequiredItems = map[uint32][]ItemS{
8: {
{ItemId: 80000001, ItemCnt: 100},
{ItemId: 80000002, ItemCnt: 20},
{ItemId: 80000003, ItemCnt: 20},
{ItemId: 80000005, ItemCnt: 20},
{ItemId: 80000011, ItemCnt: 20},
},
7: {
{ItemId: 80000001, ItemCnt: 20},
{ItemId: 80000002, ItemCnt: 20},
{ItemId: 80000003, ItemCnt: 100},
{ItemId: 80000005, ItemCnt: 20},
{ItemId: 80000011, ItemCnt: 20},
},
1: {
{ItemId: 80000001, ItemCnt: 20},
{ItemId: 80000002, ItemCnt: 100},
{ItemId: 80000003, ItemCnt: 20},
{ItemId: 80000005, ItemCnt: 20},
{ItemId: 80000011, ItemCnt: 20},
},
}
// DASHIbei 处理控制器请求。
func (h Controller) DASHIbei(req *C2s_MASTER_REWARDS, c *player.Player) (result *S2C_MASTER_REWARDS, err errorcode.ErrorCode) {
_ = req
result = &S2C_MASTER_REWARDS{}
items := c.Service.Item.Get(masterCupRewardItemMin, masterCupRewardItemMax)
result.Reward = buildMasterCupRewards(items)
return
}
// DASHIbeiR 处理控制器请求。
func (h Controller) DASHIbeiR(req *C2s_MASTER_REWARDSR, c *player.Player) (result *S2C_MASTER_REWARDSR, err errorcode.ErrorCode) {
result = &S2C_MASTER_REWARDSR{}
requiredItems, ok := masterCupRequiredItems[req.ElementType]
if !ok {
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
}
taskInfo := task.GetTaskInfo(int(masterCupTaskID), int(req.ElementType))
if taskInfo == nil {
return nil, errorcode.ErrorCodes.ErrNeedCompleteTaskForPrize
}
if !hasEnoughMasterCupItems(c, requiredItems) {
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrInsufficientItems)
}
result.ItemList = make([]data.ItemInfo, 0, len(taskInfo.ItemList))
taskData, taskErr := c.Service.Task.GetTask(masterCupTaskID)
if taskErr != nil {
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
}
progress := bitset32.From(taskData.Data)
if progress.Test(uint(req.ElementType)) {
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrAwardAlreadyClaimed)
}
if err := consumeMasterCupItems(c, requiredItems); err != nil {
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrInsufficientItems)
}
progress.Set(uint(req.ElementType))
taskData.Data = progress.Bytes()
if taskErr = c.Service.Task.SetTask(taskData); taskErr != nil {
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
}
if taskInfo.Pet != nil {
c.Service.Pet.PetAdd(taskInfo.Pet, 0)
result.CaptureTime = taskInfo.Pet.CatchTime
result.PetTypeId = taskInfo.Pet.ID
}
appendMasterCupRewardItems(c, result, taskInfo.ItemList)
return
}
// ItemS 定义请求或响应数据结构。
type ItemS struct {
ItemId uint32
ItemCnt uint32
}
func buildMasterCupRewards(items []model.Item) []uint32 {
itemCounts := make(map[uint32]uint32, len(items))
for _, item := range items {
itemCounts[item.ItemId] = uint32(item.ItemCnt)
}
rewards := make([]uint32, len(masterCupRewardElementOrder))
for i, elementType := range masterCupRewardElementOrder {
rewards[i] = itemCounts[masterCupRewardItemMin+elementType]
}
return rewards
}
func hasEnoughMasterCupItems(c *player.Player, requiredItems []ItemS) bool {
for _, item := range requiredItems {
if c.Service.Item.CheakItem(item.ItemId) < int64(item.ItemCnt) {
return false
}
}
return true
}
func consumeMasterCupItems(c *player.Player, requiredItems []ItemS) error {
for _, item := range requiredItems {
if err := c.Service.Item.UPDATE(item.ItemId, -int(item.ItemCnt)); err != nil {
return err
}
}
return nil
}
func appendMasterCupRewardItems(c *player.Player, result *S2C_MASTER_REWARDSR, itemList []data.ItemInfo) {
for _, item := range itemList {
if c.ItemAdd(item.ItemId, item.ItemCnt) {
result.ItemList = append(result.ItemList, item)
}
}
}
// C2s_MASTER_REWARDS 定义请求或响应数据结构。
type C2s_MASTER_REWARDS struct {
Head common.TomeeHeader `cmd:"2611" struc:"skip"` //玩家登录
}
// OutInfo 表示地图热度的出站消息
type S2C_MASTER_REWARDS struct {
ReLen uint32 `struc:"sizeof=Reward"`
Reward []uint32 `json:"Reward"`
}
// C2s_MASTER_REWARDSR 定义请求或响应数据结构。
type C2s_MASTER_REWARDSR struct {
Head common.TomeeHeader `cmd:"2612" struc:"skip"` //玩家登录
ElementType uint32
}
// OutInfo 表示地图热度的出站消息
type S2C_MASTER_REWARDSR struct {
BounsID uint32
PetTypeId uint32
CaptureTime uint32
ItemListLen uint32 `struc:"sizeof=ItemList"`
ItemList []data.ItemInfo `json:"Reward"`
}

View File

@@ -3,7 +3,8 @@ package controller
import (
"blazing/common/data"
"blazing/common/socket/errorcode"
"blazing/logic/service/egg"
"blazing/logic/service/common"
"blazing/logic/service/player"
"blazing/modules/config/service"
"blazing/modules/player/model"
@@ -11,7 +12,8 @@ import (
"github.com/gogf/gf/v2/util/grand"
)
func (h Controller) EggGamePlay(data1 *egg.C2S_EGG_GAME_PLAY, c *player.Player) (result *egg.S2C_EGG_GAME_PLAY, err errorcode.ErrorCode) {
// EggGamePlay 处理控制器请求。
func (h Controller) EggGamePlay(data1 *C2S_EGG_GAME_PLAY, c *player.Player) (result *S2C_EGG_GAME_PLAY, err errorcode.ErrorCode) {
switch data1.EggNum {
case 2:
@@ -21,33 +23,52 @@ func (h Controller) EggGamePlay(data1 *egg.C2S_EGG_GAME_PLAY, c *player.Player)
data1.EggNum = 10
}
r := c.Service.Item.CheakItem(400501)
if r < int32(data1.EggNum) {
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrGachaTicketsInsufficient)
if data1.EggNum > 10 || data1.EggNum <= 0 {
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError)
}
result = &egg.S2C_EGG_GAME_PLAY{ListInfo: []data.ItemInfo{}}
if r <= 0 || data1.EggNum > r {
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrGachaTicketsInsufficient)
}
if err := c.Service.Item.UPDATE(400501, int(-data1.EggNum)); err != nil {
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrGachaTicketsInsufficient)
}
result = &S2C_EGG_GAME_PLAY{ListInfo: []data.ItemInfo{}}
if grand.Meet(int(data1.EggNum), 100) {
r := service.NewPetRewardService().GetEgg()
newPet := model.GenPetInfo(int(r.MonID), int(r.DV), int(r.Nature), int(r.Effect), int(r.Lv), nil)
if grand.Meet(int(data1.EggNum), 100) {
newPet.RandShiny()
newPet := model.GenPetInfo(int(r.MonID), int(r.DV), int(r.Nature), int(r.Effect), int(r.Lv), nil, 0)
if grand.Meet(1, 500) {
newPet.RandomByWeightShiny()
}
c.Service.Pet.PetAdd(newPet)
c.Service.Pet.PetAdd(newPet, 0)
result.HadTime = newPet.CatchTime
result.PetID = newPet.ID
}
items := service.NewItemService().GetEgg(data1.EggNum)
for _, item := range items {
if item.ItemId == 0 {
continue
}
c.ItemAdd(item.ItemId, item.ItemCnt)
items := service.NewItemService().GetEgg(int(data1.EggNum))
addedItems := c.ItemAddBatch(items)
for _, item := range addedItems {
result.ListInfo = append(result.ListInfo, data.ItemInfo{ItemId: item.ItemId, ItemCnt: item.ItemCnt})
}
c.Service.Item.UPDATE(400501, int(-data1.EggNum))
return
}
// C2S_EGG_GAME_PLAY 前端向后端发送的抽蛋请求结构体
// 对应原 C# 的 C2S_EGG_GAME_PLAY
type C2S_EGG_GAME_PLAY struct {
Head common.TomeeHeader `cmd:"3201" struc:"skip"`
EggNum int64 `struc:"uint32"` // 抽蛋次数标识1 = 1次 2 = 5次 3 = 10次
// 注Go 中 uint 是平台相关类型32/64位游戏开发中推荐用 uint32 明确匹配 C# 的 uint
}
// S2C_EGG_GAME_PLAY 后端向前端返回的抽蛋结果结构体
// 对应原 C# 的 S2C_EGG_GAME_PLAY
type S2C_EGG_GAME_PLAY struct {
GiftIN uint32 `struc:"uint32"`
PetID uint32 `struc:"uint32"` // 抽中精灵的id
HadTime uint32 `struc:"uint32"` // 抽中精灵的捕捉时间(若为时间戳,建议改为 uint64
ListInfoLen uint32 `struc:"sizeof=ListInfo"`
ListInfo []data.ItemInfo `json:"listinfo"` // 抽中物品的物品数组
}

View File

@@ -0,0 +1,87 @@
package controller
import (
"blazing/common/socket/errorcode"
"blazing/logic/service/common"
"blazing/logic/service/player"
"blazing/modules/player/model"
"math/rand"
"time"
"github.com/gogf/gf/v2/util/grand"
)
// Draw15To10WithBitSet 15抽10返回标记抽取结果的uint32位1表示选中
// 规则uint32的第n位0≤n≤14=1 → 选中第n+1号元素
func Draw15To10WithBitSet() uint32 {
// 初始化随机数生成器
r := rand.New(rand.NewSource(time.Now().UnixNano()))
var resultBits uint32 // 核心结果:用位标记选中的元素
selectedCount := 0 // 已选中的数量
// 循环直到选中10个元素
for selectedCount < 10 {
// 随机生成0~14的位索引对应1~15号元素
randBitIdx := r.Intn(15)
// 构造掩码仅第randBitIdx位为1
mask := uint32(1) << randBitIdx
// 检查该位是否未被选中(避免重复)
if (resultBits & mask) == 0 {
resultBits |= mask // 标记该位为选中
selectedCount++ // 选中数+1
}
}
return resultBits
}
// GET_XUANCAI 处理控制器请求。
func (h Controller) GET_XUANCAI(data *C2s_GET_XUANCAI, c *player.Player) (result *S2C_GET_XUANCAI, err errorcode.ErrorCode) {
result = &S2C_GET_XUANCAI{}
selectedCount := 0 // 已选中的数量
res := c.Info.GetTask(13) //第一期
if res == model.Completed {
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrDailyGiftLimit)
}
c.Info.SetTask(13, model.Completed)
selectedItems := make([]uint32, 0, 10)
itemMask := make(map[uint32]uint32, 10)
// 循环直到选中10个元素
for selectedCount < 10 {
// 随机生成0~14的位索引对应1~15号元素
randBitIdx := grand.Intn(15)
// 构造掩码仅第randBitIdx位为1
mask := uint32(1) << randBitIdx
// 检查该位是否未被选中(避免重复)
if (result.Status & mask) == 0 {
result.Status |= mask
itemID := uint32(400686 + randBitIdx + 1)
selectedItems = append(selectedItems, itemID)
itemMask[itemID] = mask
selectedCount++ // 选中数+1
}
}
successItems, addErr := c.Service.Item.AddUniqueItems(selectedItems)
if addErr != nil {
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrSystemError200007)
}
for _, itemID := range successItems {
result.Status |= itemMask[itemID]
}
return
}
// C2s_GET_XUANCAI 定义请求或响应数据结构。
type C2s_GET_XUANCAI struct {
Head common.TomeeHeader `cmd:"60001" struc:"skip"` //玩家登录
}
// OutInfo 表示地图热度的出站消息
type S2C_GET_XUANCAI struct {
Status uint32
}

View File

@@ -0,0 +1,53 @@
package controller
import (
"blazing/common/socket/errorcode"
"blazing/logic/service/common"
"blazing/logic/service/player"
"blazing/modules/config/service"
"github.com/samber/lo"
)
// 进入超时空隧道
func (h Controller) TimeMap(data *C2s_SP, c *player.Player) (result *S2C_SP, err errorcode.ErrorCode) {
result = &S2C_SP{}
maps := service.NewMapService().GetTimeMap()
result.MapList = make([]ServerInfo, len(maps))
for i, v := range maps {
result.MapList[i].ID = v.MapID
result.MapList[i].DropItemIds = v.DropItemIds
pits := service.NewMapPitService().GetDataALL(v.MapID)
for _, v := range pits {
result.MapList[i].Pet = append(result.MapList[i].Pet, v.RefreshID...)
}
result.MapList[i].Pet = lo.Union(result.MapList[i].Pet)
}
return
}
// C2s_SP 定义请求或响应数据结构。
type C2s_SP struct {
Head common.TomeeHeader `cmd:"60002" struc:"skip"` //超时空地图
}
// OutInfo 表示地图热度的出站消息
type S2C_SP struct {
MapListLen uint32 `struc:"sizeof=MapList"`
MapList []ServerInfo
}
// ServerInfo 定义请求或响应数据结构。
type ServerInfo struct {
ID uint32 //地图ID
PetLen uint32 `struc:"sizeof=Pet"`
Pet []uint32 //拥有的精灵
DropItemIdsLen uint32 `struc:"sizeof=DropItemIds"`
DropItemIds []uint32 //掉落物
GameLen uint32 `struc:"sizeof=Game"`
Game string
}

View File

@@ -2,12 +2,14 @@ package controller
import (
"blazing/common/socket/errorcode"
"blazing/logic/service/leiyi"
"blazing/logic/service/common"
"blazing/logic/service/player"
)
func (h Controller) GetLeiyiTrainStatus(data *leiyi.C2s_LEIYI_TRAIN_GET_STATUS, c *player.Player) (result *leiyi.S2C_LEIYI_TRAIN_GET_STATUS, err errorcode.ErrorCode) {
result = &leiyi.S2C_LEIYI_TRAIN_GET_STATUS{}
// GetLeiyiTrainStatus 处理控制器请求。
func (h Controller) GetLeiyiTrainStatus(data *C2s_LEIYI_TRAIN_GET_STATUS, c *player.Player) (result *S2C_LEIYI_TRAIN_GET_STATUS, err errorcode.ErrorCode) {
result = &S2C_LEIYI_TRAIN_GET_STATUS{}
for i := 0; i < 6; i++ {
result.Status[i].Total = 10
@@ -17,3 +19,21 @@ func (h Controller) GetLeiyiTrainStatus(data *leiyi.C2s_LEIYI_TRAIN_GET_STATUS,
return
}
// C2s_LEIYI_TRAIN_GET_STATUS 定义请求或响应数据结构。
type C2s_LEIYI_TRAIN_GET_STATUS struct {
Head common.TomeeHeader `cmd:"2393" struc:"skip"` //玩家登录
}
// OutInfo 表示地图热度的出站消息
type S2C_LEIYI_TRAIN_GET_STATUS struct {
Status [10]S2C_LEIYI_TRAIN_GET_STATUS_info `json:"status"`
}
// S2C_LEIYI_TRAIN_GET_STATUS_info 定义请求或响应数据结构。
type S2C_LEIYI_TRAIN_GET_STATUS_info struct {
// Today uint32 // 今日训练HP次数
Current uint32 // 当前训练HP次数
Total uint32 // 目标训练HP次数
}

View File

@@ -2,8 +2,9 @@ package controller
import (
"blazing/common/socket/errorcode"
"blazing/cool"
"blazing/logic/service/common"
"blazing/logic/service/fight"
"blazing/logic/service/pet"
"blazing/logic/service/player"
)
@@ -11,10 +12,30 @@ import (
// data: 空输入结构
// c: 当前玩家对象
// 返回: 捕捉结果消耗的EV值和错误码
func (h Controller) HanLiuQiang(data *pet.C2S_2608, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
func (h Controller) HanLiuQiang(data *C2S_2608, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if c.ItemAdd(100245, 1) {
return
}
if cool.Config.ServerInfo.IsVip == 0 {
return
}
c.ItemAdd(500655, 1)
// pet := model.GenPetInfo(426, 31, -1, -1, 100, nil, 0)
// c.Service.Pet.PetAdd(pet, 0)
// pet = model.GenPetInfo(1567, 31, -1, -1, 100, nil, 0)
// c.Service.Pet.PetAdd(pet)
// pet = model.GenPetInfo(1905, 31, -1, -1, 100, nil, 0)
// c.Service.Pet.PetAdd(pet)
return result, -1
}
// C2S_2608 定义请求或响应数据结构。
type C2S_2608 struct {
Head common.TomeeHeader `cmd:"2608" struc:"skip"`
}

View File

@@ -0,0 +1,30 @@
package controller
import (
"blazing/common/data/xmlres"
"blazing/common/socket/errorcode"
"blazing/logic/service/player"
)
func buySeerdouBackpackItem(player *player.Player, itemID int64, count int64) (bought bool, err errorcode.ErrorCode) {
if itemID <= 0 || count <= 0 {
return false, errorcode.ErrorCodes.ErrSystemError
}
itemInfo, exists := xmlres.ItemsMAP[int(itemID)]
if !exists {
return false, 0
}
totalCost := int64(itemInfo.Price) * count
if totalCost > 0 && !player.GetCoins(totalCost) {
return false, errorcode.ErrorCodes.ErrSunDouInsufficient10016
}
if !player.ItemAdd(itemID, count) {
return false, 0
}
player.Info.Coins -= totalCost
return true, 0
}

View File

@@ -17,74 +17,158 @@ func (h Controller) checkFightStatus(c *player.Player) errorcode.ErrorCode {
}
// OnReadyToFight 准备战斗
func (h Controller) OnReadyToFight(data *fight.ReadyToFightInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
func (h Controller) OnReadyToFight(data *ReadyToFightInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if err := h.checkFightStatus(c); err != 0 {
return nil, err
}
defer c.FightC.ReadyFight(c)
go c.FightC.ReadyFight(c)
return nil, -1
}
// GroupReadyFightFinish 旧组队协议准备完成。
func (h Controller) GroupReadyFightFinish(data *GroupReadyFightFinishInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if err := h.checkFightStatus(c); err != 0 {
return nil, err
}
go c.FightC.ReadyFight(c)
return nil, -1
}
func (h Controller) GroupUseSkill(data *GroupUseSkillInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if err := h.checkFightStatus(c); err != 0 {
return nil, err
}
targetRelation := fight.SkillTargetOpponent
if data.TargetSide == 1 {
targetRelation = fight.SkillTargetAlly
}
h.dispatchFightActionEnvelope(c, fight.NewSkillActionEnvelope(data.SkillId, int(data.ActorIndex), int(data.TargetPos), targetRelation, 0))
c.SendPackCmd(7558, nil)
return nil, -1
}
func (h Controller) GroupUseItem(data *GroupUseItemInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if err := h.checkFightStatus(c); err != 0 {
return nil, err
}
h.dispatchFightActionEnvelope(c, fight.NewItemActionEnvelope(0, data.ItemId, int(data.ActorIndex), int(data.ActorIndex), fight.SkillTargetSelf))
return nil, -1
}
func (h Controller) GroupChangePet(data *GroupChangePetInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if err := h.checkFightStatus(c); err != 0 {
return nil, err
}
h.dispatchFightActionEnvelope(c, fight.NewChangeActionEnvelope(data.CatchTime, int(data.ActorIndex)))
return nil, -1
}
func (h Controller) GroupEscape(data *GroupEscapeInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if err := h.checkFightStatus(c); err != 0 {
return nil, err
}
if fightC, ok := c.FightC.(*fight.FightC); ok && fightC != nil && fightC.LegacyGroupProtocol {
fightC.SendLegacyEscapeSuccess(c, int(data.ActorIndex))
}
h.dispatchFightActionEnvelope(c, fight.NewEscapeActionEnvelope())
return nil, 0
}
func (h Controller) GroupFightWinClose(data *GroupFightWinCloseInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if c != nil {
c.QuitFight()
}
return nil, -1
}
func (h Controller) GroupFightTimeoutExit(data *GroupFightTimeoutExitInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if c != nil {
c.QuitFight()
}
return nil, -1
}
// UseSkill 使用技能包
func (h Controller) UseSkill(data *fight.UseSkillInInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
func (h Controller) UseSkill(data *UseSkillInInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if err := h.checkFightStatus(c); err != 0 {
return nil, err
}
defer c.FightC.UseSkill(c, data.SkillId)
h.dispatchFightActionEnvelope(c, buildLegacyUseSkillEnvelope(data))
return nil, 0
}
// UseSkillAt 组队/多战位技能包cmd=7505
// 目标关系0=对方 1=自己 2=队友。
func (h Controller) UseSkillAt(data *UseSkillAtInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if err := h.checkFightStatus(c); err != 0 {
return nil, err
}
h.dispatchFightActionEnvelope(c, buildIndexedUseSkillEnvelope(data))
return nil, 0
}
// Escape 战斗逃跑
func (h Controller) Escape(data *fight.EscapeFightInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
func (h Controller) Escape(data *EscapeFightInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if err := h.checkFightStatus(c); err != 0 {
return nil, err
}
defer c.FightC.Over(c, info.BattleOverReason.PlayerEscape)
h.dispatchFightActionEnvelope(c, buildLegacyEscapeEnvelope())
return nil, 0
}
// ChangePet 切换精灵
func (h Controller) ChangePet(data *fight.ChangePetInboundInfo, c *player.Player) (result *info.ChangePetInfo, err errorcode.ErrorCode) {
func (h Controller) ChangePet(data *ChangePetInboundInfo, c *player.Player) (result *info.ChangePetInfo, err errorcode.ErrorCode) {
if err := h.checkFightStatus(c); err != 0 {
return nil, err
}
defer c.FightC.ChangePet(c, data.CatchTime)
h.dispatchFightActionEnvelope(c, buildLegacyChangeEnvelope(data))
return nil, -1
}
// Capture 捕捉精灵
func (h Controller) Capture(data *fight.CatchMonsterInboundInfo, c *player.Player) (result *info.CatchMonsterOutboundInfo, err errorcode.ErrorCode) {
func (h Controller) Capture(data *CatchMonsterInboundInfo, c *player.Player) (result *info.CatchMonsterOutboundInfo, err errorcode.ErrorCode) {
if err := h.checkFightStatus(c); err != 0 {
return nil, err
}
defer c.FightC.Capture(c, data.CapsuleId)
if c.GetSpace().IsTime {
if data.CapsuleId < 300009 {
go c.FightC.UseSkill(c, 0)
return nil, -1
}
}
go c.FightC.Capture(c, data.CapsuleId)
return nil, -1
}
// LoadPercent 加载进度
func (h Controller) LoadPercent(data *fight.LoadPercentInboundInfo, c *player.Player) (result *info.LoadPercentOutboundInfo, err errorcode.ErrorCode) {
func (h Controller) LoadPercent(data *LoadPercentInboundInfo, c *player.Player) (result *info.LoadPercentOutboundInfo, err errorcode.ErrorCode) {
if c.FightC == nil {
return nil, -1
}
defer c.FightC.LoadPercent(c, int32(data.Percent))
go c.FightC.LoadPercent(c, int32(data.Percent))
return nil, -1
}
// UsePetItemInboundInfo 使用宠物道具
func (h Controller) UsePetItemInboundInfo(data *fight.UsePetItemInboundInfo, c *player.Player) (result *info.UsePetIteminfo, err errorcode.ErrorCode) {
func (h Controller) UsePetItemInboundInfo(data *UsePetItemInboundInfo, c *player.Player) (result *info.UsePetIteminfo, err errorcode.ErrorCode) {
if err := h.checkFightStatus(c); err != 0 {
return nil, err
}
defer c.FightC.UseItem(c, data.CatchTime, data.ItemId)
if c.GetSpace().IsTime {
if data.ItemId < 300009 {
go c.FightC.UseSkill(c, 0)
}
}
h.dispatchFightActionEnvelope(c, buildLegacyUseItemEnvelope(data))
return nil, -1
}
// FightChat 战斗聊天
func (h Controller) FightChat(data *fight.ChatInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
func (h Controller) FightChat(data *ChatInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if err := h.checkFightStatus(c); err != 0 {
return nil, err
}
defer c.FightC.Chat(c, data.Message)
h.dispatchFightActionEnvelope(c, buildChatEnvelope(data))
return nil, -1
}

View File

@@ -1,241 +0,0 @@
package controller
import (
"blazing/common/data"
"blazing/common/data/xmlres"
"blazing/common/socket/errorcode"
"strings"
"blazing/logic/service/fight"
"blazing/logic/service/fight/info"
"blazing/logic/service/player"
"blazing/modules/config/service"
"blazing/modules/player/model"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
"github.com/samber/lo"
)
// processMonID 处理怪物ID字符串如果是多个ID则随机选择一个
func processMonID(bm string) string {
// 按空格分割字符串
monid := strings.Split(bm, " ")
// 过滤分割后可能的空字符串(如连续空格导致的空元素)
filtered := make([]string, 0, len(monid))
for _, m := range monid {
if m != "" {
filtered = append(filtered, m)
}
}
monid = filtered
var selected string
switch len(monid) {
case 0:
// 无元素时,可返回空或默认值(根据业务需求调整)
selected = ""
case 1:
// 长度为1时取第一个唯一的元素
selected = monid[0]
default:
// 长度大于1时随机选取一个
randomIdx := grand.Intn(len(monid))
selected = monid[randomIdx]
}
return selected
}
// PlayerFightBoss 挑战地图boss
// data: 包含挑战Boss信息的输入数据
// player: 当前玩家对象
// 返回: 战斗结果和错误码
func (Controller) PlayerFightBoss(data *fight.ChallengeBossInboundInfo, p *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if !p.CanFight() {
return nil, errorcode.ErrorCodes.ErrPokemonNoStamina
}
var monster *model.PetInfo
monsterInfo := &model.PlayerInfo{}
var taskID int
var canCapture int
mdata, ok := xmlres.MonsterMap[int(p.Info.MapID)]
if !ok {
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
}
if len(mdata.Bosses) == 0 {
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
}
for _, bc := range mdata.Bosses {
if bc.Id == nil {
bc.Id = gconv.PtrInt(0)
}
if (bc.Id == nil && data.BossId == 0) || uint32(*bc.Id) == data.BossId { //打默认第一个boss
if bc.TaskID != nil {
taskID = *bc.TaskID
}
for i, bm := range bc.BossMon {
monster = model.GenPetInfo(
gconv.Int(processMonID(bm.MonID)), 24, //24个体
-1,
0, //野怪没特性
bm.Lv, nil)
monster.CatchTime = uint32(i)
if bm.Hp != 0 {
monster.Hp = uint32(bm.Hp)
monster.MaxHp = uint32(bm.Hp)
}
for _, v := range strings.Split(bm.NewSeIdxs, " ") {
idx := gconv.Uint16(v)
if idx == 0 {
continue
}
EID, args := service.NewEffectService().Args(uint32(idx))
monster.EffectInfo = append(monster.EffectInfo, model.PetEffectInfo{
Idx: idx,
EID: gconv.Uint16(EID),
Args: gconv.Ints(args),
})
}
monsterInfo.PetList = append(monsterInfo.PetList, *monster)
}
if bc.BossCatchable == 1 {
canCapture = xmlres.PetMAP[int(monster.ID)].CatchRate
if grand.Meet(1, 100) {
r := monsterInfo.PetList[0]
r.RandShiny()
monsterInfo.PetList[0] = r
}
}
monsterInfo.Nick = bc.Name //xmlres.PetMAP[int(monster.ID)].DefName
break
}
}
if len(monsterInfo.PetList) == 0 {
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
}
p.Fightinfo.Status = info.BattleMode.FIGHT_WITH_NPC
p.Fightinfo.Mode = info.BattleMode.MULTI_MODE
ai := player.NewAI_player(monsterInfo)
ai.CanCapture = canCapture
ai.Prop[0] = 2
fight.NewFight(p, ai, func(foi info.FightOverInfo) {
if taskID != 0 {
if foi.Reason == 0 && foi.WinnerId == p.Info.UserID {
p.SptCompletedTask(taskID, 1)
}
}
//p.Done.Exec(model.MilestoneMode.BOSS, []uint32{p.Info.MapID, data.BossId, uint32(foi.Reason)}, nil)
})
return nil, -1
}
// OnPlayerFightNpcMonster 战斗野怪
// data: 包含战斗野怪信息的输入数据
// player: 当前玩家对象
// 返回: 战斗结果和错误码
func (Controller) OnPlayerFightNpcMonster(data1 *fight.FightNpcMonsterInboundInfo, p *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if !p.CanFight() {
return nil, errorcode.ErrorCodes.ErrSystemError
}
refPet := p.OgreInfo.Data[data1.Number]
if refPet.Id == 0 {
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
}
if refPet.Ext != 0 {
refPet.Id = refPet.Ext
}
monster := model.GenPetInfo(
int(refPet.Id), -1,
-1,
0, //野怪没特性
int(refPet.Lv),
refPet.ShinyInfo)
if refPet.Ext != 0 {
if grand.Meet(3, 100) {
monster.RandShiny()
}
}
monsterInfo := &model.PlayerInfo{}
monsterInfo.Nick = xmlres.PetMAP[int(monster.ID)].DefName
monsterInfo.PetList = append(monsterInfo.PetList, *monster)
ai := player.NewAI_player(monsterInfo)
ai.CanCapture = handleNPCFightSpecial(monster.ID)
p.Fightinfo.Status = info.BattleMode.FIGHT_WITH_NPC //打野怪
p.Fightinfo.Mode = info.BattleMode.MULTI_MODE //多人模式
fight.NewFight(p, ai, func(foi info.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 {
if !p.CanGetExp() {
return
}
exp := uint32(xmlres.PetMAP[int(monster.ID)].YieldingExp) * monster.Level / 7
items := &info.S2C_GET_BOSS_MONSTER{
//EV: 45,
EXP: exp * 2,
}
if refPet.Item != 0 {
count := uint32(grand.Intn(2) + 1)
p.ItemAdd(refPet.Item, count)
items.ItemList = append(items.ItemList, data.ItemInfo{
ItemId: refPet.Item,
ItemCnt: count,
})
}
evs := gconv.Uint32s(strings.Split(xmlres.PetMAP[int(monster.ID)].YieldingEV, " "))
items.EV = lo.Sum(evs) - 1
p.Info.EVPool += lo.Sum(evs) //给予累计学习力
foi.Winpet.AddEV(evs)
p.Info.ExpPool += exp * 4
p.AddPetExp(foi.Winpet, uint32(exp)*2)
p.SendPackCmd(8004, items)
}
})
return nil, -1
}
// handleNPCFightSpecial 处理NPC战斗特殊情况
func handleNPCFightSpecial(petID uint32) int {
npcPetID := int(petID)
petCfg, ok := xmlres.PetMAP[npcPetID]
if !ok {
// log.Error(context.Background(), "NPC宠物配置不存在", "petID", npcPetID)
return 0
}
catchRate := gconv.Int(petCfg.CatchRate)
return catchRate
}

View File

@@ -0,0 +1,314 @@
package controller
import (
"blazing/common/data"
"blazing/common/data/xmlres"
"blazing/common/socket/errorcode"
"blazing/logic/service/fight"
fightinfo "blazing/logic/service/fight/info"
"blazing/logic/service/fight/input"
"blazing/logic/service/player"
configmodel "blazing/modules/config/model"
"blazing/modules/config/service"
"blazing/modules/player/model"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/grand"
)
const (
rewardItemExpPool = 3
groupBossSlotLimit = 3
)
// PlayerFightBoss 挑战地图boss
func (Controller) PlayerFightBoss(req *ChallengeBossInboundInfo, p *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if err = p.CanFight(); err != 0 {
return nil, err
}
mapNode := p.GetSpace().GetMatchedMapNode(req.BossId)
if mapNode == nil {
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
}
bossConfigs, err := loadMapBossConfigs(mapNode)
if err != 0 {
return nil, err
}
monsterInfo, leadMonsterID, err := buildBossMonsterInfo(mapNode.NodeName, bossConfigs)
if err != 0 {
return nil, err
}
p.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
p.Fightinfo.Mode = resolveMapNodeFightMode(mapNode)
ai := player.NewAI_player(monsterInfo)
ai.CanCapture = resolveBossCaptureRate(bossConfigs[0].IsCapture, leadMonsterID)
ai.BossScript = bossConfigs[0].Script
ai.AddBattleProp(0, 2)
var fightC *fight.FightC
fightC, err = startMapBossFight(mapNode, p, ai, func(foi model.FightOverInfo) {
if mapNode.WinBonusID == 0 {
return
}
if shouldGrantBossWinBonus(fightC, p.Info.UserID, bossConfigs[0], foi) {
p.SptCompletedTask(mapNode.WinBonusID, 1)
}
})
if err != 0 {
return nil, err
}
return nil, -1
}
func startMapBossFight(
mapNode *configmodel.MapNode,
p *player.Player,
ai *player.AI_player,
fn func(model.FightOverInfo),
) (*fight.FightC, errorcode.ErrorCode) {
ourPets := p.GetPetInfo(p.CurrentMapPetLevelLimit())
oppPets := ai.GetPetInfo(0)
if mapNode != nil && mapNode.IsGroupBoss != 0 {
if len(ourPets) > 0 && len(oppPets) > 0 {
slotLimit := groupBossSlotLimit
if mapNode.PkFlag != 0 {
slotLimit = 1
}
return fight.NewLegacyGroupFightSingleController(p, ai, ourPets, oppPets, slotLimit, fn)
}
}
return fight.NewFight(p, ai, ourPets, oppPets, fn)
}
func resolveMapNodeFightMode(mapNode *configmodel.MapNode) uint32 {
if mapNode != nil && mapNode.PkFlag != 0 {
return fightinfo.BattleMode.SINGLE_MODE
}
return fightinfo.BattleMode.MULTI_MODE
}
// OnPlayerFightNpcMonster 战斗野怪
func (Controller) OnPlayerFightNpcMonster(req *FightNpcMonsterInboundInfo, p *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if err = p.CanFight(); err != 0 {
return nil, err
}
if int(req.Number) >= len(p.Data) {
return nil, errorcode.ErrorCodes.ErrPokemonNotHere
}
refPet := p.Data[req.Number]
monster, monsterInfo, err := buildNpcMonsterInfo(refPet, p.Info.MapID)
if err != 0 {
return nil, err
}
ai := player.NewAI_player(monsterInfo)
ai.CanCapture = refPet.IsCapture
p.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
p.Fightinfo.Mode = fightinfo.BattleMode.MULTI_MODE
_, err = fight.NewFight(p, ai, p.GetPetInfo(p.CurrentMapPetLevelLimit()), ai.GetPetInfo(0), func(foi model.FightOverInfo) {
handleNpcFightRewards(p, foi, monster)
})
if err != 0 {
return nil, err
}
return nil, -1
}
func loadMapBossConfigs(mapNode *configmodel.MapNode) ([]configmodel.BossConfig, errorcode.ErrorCode) {
if mapNode == nil || len(mapNode.BossIds) == 0 {
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
}
bossID := mapNode.BossIds[0]
if len(mapNode.BossIds) > 1 {
bossID = mapNode.BossIds[grand.Intn(len(mapNode.BossIds))]
}
bossConfigs := service.NewBossService().Get(bossID)
if len(bossConfigs) == 0 {
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
}
return bossConfigs, 0
}
func buildBossMonsterInfo(nodeName string, bossConfigs []configmodel.BossConfig) (*model.PlayerInfo, uint32, errorcode.ErrorCode) {
monsterInfo := &model.PlayerInfo{Nick: nodeName}
var leadMonsterID uint32
for i, bossConfig := range bossConfigs {
dv, generation := bossFightPetArgs(bossConfig.IsCapture)
monster := model.GenPetInfo(
gconv.Int(bossConfig.MonID),
dv,
-1,
0,
int(bossConfig.Lv),
nil,
generation,
)
if monster == nil {
return nil, 0, errorcode.ErrorCodes.ErrPokemonNotExists
}
monster.CatchTime = uint32(i)
monster.ConfigBoss(bossConfig.PetBaseConfig)
appendPetEffects(monster, bossConfig.Effect)
if i == 0 {
leadMonsterID = monster.ID
}
monsterInfo.PetList = append(monsterInfo.PetList, *monster)
}
if len(monsterInfo.PetList) == 0 {
return nil, 0, errorcode.ErrorCodes.ErrPokemonNotExists
}
if bossConfigs[0].IsCapture == 1 {
monsterInfo.PetList[0].ShinyInfo = make([]data.GlowFilter, 0)
if grand.Meet(1, 500) {
monsterInfo.PetList[0].RandomByWeightShiny()
}
}
return monsterInfo, leadMonsterID, 0
}
func bossFightPetArgs(canCapture int) (dv int, generation int) {
if canCapture == 1 {
return -1, -1
}
return 24, 0
}
func appendPetEffects(monster *model.PetInfo, effectIDs []uint32) {
effects := service.NewEffectService().Args(effectIDs)
for _, effect := range effects {
monster.EffectInfo = append(monster.EffectInfo, model.PetEffectInfo{
Idx: uint16(effect.ID),
EID: gconv.Uint16(effect.Eid),
Args: gconv.Ints(effect.Args),
})
}
}
func resolveBossCaptureRate(canCapture int, petID uint32) int {
if canCapture == 0 {
return 0
}
petCfg, ok := xmlres.PetMAP[int(petID)]
if !ok || petCfg.CatchRate == 0 {
return 0
}
return petCfg.CatchRate
}
func shouldGrantBossWinBonus(fightC *fight.FightC, playerID uint32, bossConfig configmodel.BossConfig, foi model.FightOverInfo) bool {
if len(bossConfig.Rule) == 0 {
return foi.Reason == 0 && foi.WinnerId == playerID
}
for _, ruleConfig := range service.NewFightRuleService().GetByRuleIdxs(bossConfig.Rule) {
rule := input.GetRule(int64(ruleConfig.RuleIdx))
if rule == nil {
continue
}
rule.SetArgs(ruleConfig.Args...)
if !rule.Exec(fightC, &foi) {
return false
}
}
return true
}
func buildNpcMonsterInfo(refPet player.OgrePetInfo, mapID uint32) (*model.PetInfo, *model.PlayerInfo, errorcode.ErrorCode) {
if refPet.ID == 0 {
return nil, nil, errorcode.ErrorCodes.ErrPokemonNotHere
}
monster := model.GenPetInfo(
refPet.GetID(),
-1,
-1,
0,
refPet.GetLevel(),
refPet.ShinyInfo,
-1,
)
if monster == nil {
return nil, nil, errorcode.ErrorCodes.ErrPokemonNotExists
}
monster.CatchMap = mapID
if refPet.Ext != 0 && grand.Meet(1, 500) {
monster.RandomByWeightShiny()
}
petCfg, ok := xmlres.PetMAP[int(monster.ID)]
if !ok {
return nil, nil, errorcode.ErrorCodes.ErrPokemonNotExists
}
monsterInfo := &model.PlayerInfo{
Nick: petCfg.DefName,
PetList: []model.PetInfo{*monster},
}
return monster, monsterInfo, 0
}
func handleNpcFightRewards(p *player.Player, foi model.FightOverInfo, monster *model.PetInfo) {
if foi.Reason != 0 || foi.WinnerId != p.Info.UserID || !p.CanGet() {
return
}
petCfg, ok := xmlres.PetMAP[int(monster.ID)]
if !ok {
return
}
exp := uint32(petCfg.YieldingExp) * monster.Level / 7
addlevel, poolevel := p.CanGetExp()
addexp := gconv.Float32(addlevel * gconv.Float32(exp))
poolexp := gconv.Float32(poolevel) * gconv.Float32(exp)
rewards := &fightinfo.S2C_GET_BOSS_MONSTER{}
p.ItemAdd(3, int64(poolexp+addexp))
rewards.AddItem(rewardItemExpPool, uint32(poolexp))
p.AddPetExp(foi.Winpet, int64(addexp))
if p.CanGetItem() {
itemID := p.GetSpace().GetDrop()
if itemID != 0 {
count := uint32(grand.N(1, 2))
if p.ItemAdd(itemID, int64(count)) {
rewards.AddItem(uint32(itemID), count)
}
}
}
petType := int64(petCfg.Type)
if monster.IsShiny() && p.CanGetXUAN() && petType < 16 {
xuanID := uint32(400686 + petType)
count := uint32(grand.N(1, 2))
if p.ItemAdd(int64(xuanID), int64(count)) {
rewards.AddItem(xuanID, count)
}
}
if rewards.HasReward() {
p.SendPackCmd(8004, rewards)
}
foi.Winpet.AddEV(petCfg.YieldingEVValues)
}

View File

@@ -2,6 +2,8 @@ package controller
import (
"blazing/common/socket/errorcode"
"blazing/modules/config/service"
"blazing/modules/player/model"
"blazing/logic/service/common"
"blazing/logic/service/fight"
@@ -11,22 +13,56 @@ import (
//大乱斗
func (h Controller) PetMelee(data *fight.StartPetWarInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
func (h Controller) PetMelee(data *StartPetWarInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
c.Fightinfo.Mode = info.BattleMode.PET_MELEE
c.Fightinfo.Status = info.BattleMode.PET_MELEE
var mepet []model.PetInfo
for i, v := range service.NewMELEEService().Def() {
if v.Lv == 0 {
v.Lv = 100
}
pet := model.GenPetInfo(int(v.MonID), 24, int(v.Nature), int(v.Effect[0]), int(v.Lv), nil, 0)
pet.ConfigBoss(v)
pet.CatchTime = c.GetInfo().UserID + uint32(i)*1000000
pet.Cure()
mepet = append(mepet, *pet)
}
if len(mepet) < 6 {
return nil, errorcode.ErrorCodes.ErrSystemError
}
err = c.JoinFight(func(p common.PlayerI) bool {
_, err = fight.NewFight(p, c, func(foi info.FightOverInfo) {
_, err = fight.NewFight(p, c, mepet[:3], mepet[3:], func(foi model.FightOverInfo) {
if foi.Reason == 0 { //我放获胜
if foi.WinnerId == c.GetInfo().UserID {
c.Info.MessWin += 1
c.MessWin(true)
p.MessWin(false)
} else {
p.GetInfo().MessWin += 1
p.MessWin(true)
c.MessWin(false)
}
}
if foi.Reason == model.BattleOverReason.PlayerOffline {
if foi.WinnerId == c.GetInfo().UserID {
p.MessWin(false)
} else {
c.MessWin(false)
}
}
}) ///开始对战,房主方以及被邀请方
return err <= 0
@@ -34,23 +70,48 @@ func (h Controller) PetMelee(data *fight.StartPetWarInboundInfo, c *player.Playe
return
}
func (h Controller) PetKing(data *fight.PetKingJoinInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
// PetKing 处理控制器请求。
func (h Controller) PetKing(data *PetKingJoinInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
c.Fightinfo.Status = info.BattleMode.PET_TOPLEVEL
// ElementTypeNumbers 是控制器层共享变量。
var ElementTypeNumbers = []int{1, 2, 3, 5, 11, 4, 6, 7, 9}
switch data.Type {
case 5:
c.Fightinfo.Mode = info.BattleMode.SINGLE_MODE
case 6:
c.Fightinfo.Mode = info.BattleMode.MULTI_MODE
case 11:
//草","水","火","电","战斗","飞行","机械","地面","冰"
// 按顺序:草、水、火、电、战斗、飞行、机械、地面、冰
//println("11", c.GetPetInfo()[0].Type(), ElementTypeNumbers[data.FightType-1])
if c.GetPetInfo(0)[0].Type() != int(ElementTypeNumbers[data.FightType-1]) {
return nil, errorcode.ErrorCode(errorcode.ErrorCodes.ErrVictoryConditionNotMet)
}
c.Fightinfo.Mode = info.BattleMode.SINGLE_MODE
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, p.GetInfo().PetList, c.GetInfo().PetList, func(foi model.FightOverInfo) {
if foi.Reason == 0 { //我放获胜
if foi.WinnerId == c.GetInfo().UserID {
c.Info.MonKingWin += 1
} else {
p.GetInfo().MonKingWin += 1
switch data.Type {
case 11:
if foi.WinnerId == c.GetInfo().UserID {
c.ItemAdd(80000000+int64(ElementTypeNumbers[data.FightType-1]), 1)
} else {
p.ItemAdd(80000000+int64(ElementTypeNumbers[data.FightType-1]), 1)
}
default:
if foi.WinnerId == c.GetInfo().UserID {
c.Info.MonKingWin += 1
} else {
p.GetInfo().MonKingWin += 1
}
}
}

View File

@@ -2,6 +2,7 @@ package controller
import (
"blazing/common/socket/errorcode"
"blazing/modules/player/model"
"sync/atomic"
"blazing/logic/service/common"
@@ -11,8 +12,8 @@ import (
)
// 接收战斗或者取消战斗的包
func (h Controller) OnPlayerHandleFightInvite(data *fight.HandleFightInviteInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if c.GetSpace().Owner.UserID == c.Info.UserID {
func (h Controller) OnPlayerHandleFightInvite(data *HandleFightInviteInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if c.IsArenaPVPLocked() {
return nil, errorcode.ErrorCodes.ErrSystemError
}
if c.GetSpace().Owner.UserID == data.UserID {
@@ -23,8 +24,9 @@ func (h Controller) OnPlayerHandleFightInvite(data *fight.HandleFightInviteInbou
}
if !c.CanFight() {
return nil, errorcode.ErrorCodes.ErrSystemError
r := c.CanFight()
if c.CanFight() != 0 {
return nil, r
}
//c.Fightinfo.Status = info.BattleMode.FIGHT_WITH_NPC
@@ -53,7 +55,9 @@ func (h Controller) OnPlayerHandleFightInvite(data *fight.HandleFightInviteInbou
return
}
_, err = fight.NewFight(v, c, func(foi info.FightOverInfo) {
_, err = fight.NewFight(v, c, v.GetPetInfo(100), c.GetPetInfo(100), func(foi model.FightOverInfo) {
//println("好友对战测试", foi.Reason)
})
@@ -74,8 +78,8 @@ func (h Controller) OnPlayerHandleFightInvite(data *fight.HandleFightInviteInbou
}
// 邀请其他人进行战斗
func (h Controller) OnPlayerInviteOtherFight(data *fight.InviteToFightInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if c.GetSpace().Owner.UserID == c.Info.UserID {
func (h Controller) OnPlayerInviteOtherFight(data *InviteToFightInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if c.IsArenaPVPLocked() {
return nil, errorcode.ErrorCodes.ErrSystemError
}
if c.GetSpace().Owner.ChallengerID == c.Info.UserID {
@@ -99,7 +103,7 @@ func (h Controller) OnPlayerInviteOtherFight(data *fight.InviteToFightInboundInf
}
// 取消队列
func (h Controller) OnPlayerCanceledOtherInviteFight(data *fight.InviteFightCancelInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
func (h Controller) OnPlayerCanceledOtherInviteFight(data *InviteFightCancelInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
atomic.StoreUint32(&c.Fightinfo.Mode, 0) //设置状态为0
return
}

View File

@@ -1,211 +0,0 @@
package controller
import (
"blazing/common/socket/errorcode"
"blazing/common/utils"
"blazing/logic/service/fight"
fightinfo "blazing/logic/service/fight/info"
"blazing/logic/service/player"
"blazing/logic/service/space/info"
configmodel "blazing/modules/config/model"
"blazing/modules/config/service"
"blazing/modules/player/model"
"sync/atomic"
"github.com/gogf/gf/v2/util/gconv"
"github.com/jinzhu/copier"
)
func (h Controller) FreshOPEN(data *fight.C2S_OPEN_DARKPORTAL, c *player.Player) (result *fight.S2C_OPEN_DARKPORTAL, err errorcode.ErrorCode) {
result = &fight.S2C_OPEN_DARKPORTAL{}
c.Info.CurrentFreshStage = utils.Max(c.Info.CurrentFreshStage, 1)
c.Info.CurrentStage = utils.Max(c.Info.CurrentStage, 1)
boss := service.NewTower110Service().Boss(uint32(data.Level))
result = &fight.S2C_OPEN_DARKPORTAL{}
for _, v := range boss.BossIds {
r := service.NewBossService().Get(v)
result.CurBossID = uint32(r.MonID)
}
c.CurDark = uint32(data.Level)
defer c.GetSpace().LeaveMap(c)
return result, 0
}
// FreshChoiceFightLevel 处理玩家选择挑战模式(试炼之塔或勇者之塔)
// 根据不同的CMD值设置玩家的挑战状态和地图信息并返回当前挑战层级信息
// 参数:
//
// data: 客户端发送的挑战层级选择请求数据包含CMD和挑战层级
// c: 玩家对象,包含玩家的详细信息
//
// 返回值:
//
// result: 服务器返回给客户端的挑战层级信息包含当前战斗层级和Boss ID
// err: 错误码,表示处理过程中是否出现错误
func (h Controller) FreshChoiceFightLevel(data *fight.C2S_FRESH_CHOICE_FIGHT_LEVEL, c *player.Player) (result *fight.S2C_FreshChoiceLevelRequestInfo, err errorcode.ErrorCode) {
result = &fight.S2C_FreshChoiceLevelRequestInfo{}
c.Info.CurrentFreshStage = utils.Max(c.Info.CurrentFreshStage, 1)
c.Info.CurrentStage = utils.Max(c.Info.CurrentStage, 1)
if data.Level > 0 {
switch data.Head.CMD {
case 2428: //试炼之塔
c.Info.CurrentFreshStage = uint32((data.Level-1)*10) + 1
case 2414: //勇者之塔
c.Info.CurrentStage = uint32((data.Level-1)*10) + 1
}
}
var boss *configmodel.BaseTowerConfig
switch data.Head.CMD {
case 2428: //试炼之塔
result.CurFightLevel = uint32(c.Info.CurrentFreshStage)
boss = service.NewTower600Service().Boss(c.Info.CurrentFreshStage)
case 2414: //勇者之塔
result.CurFightLevel = uint32(c.Info.CurrentStage)
boss = service.NewTower500Service().Boss(c.Info.CurrentStage)
//next := service.NewTower600Service().Boss(c.Info.CurrentFreshStage + 1)
}
if boss != nil {
for _, v := range boss.BossIds {
r := service.NewBossService().Get(v)
result.BossId = append(result.BossId, uint32(r.MonID))
}
}
// 重置玩家的Canmon标志位为0表示可以刷怪
atomic.StoreUint32(&c.Canmon, 0)
// 在函数结束时将玩家传送到对应地图
defer c.GetSpace().LeaveMap(c)
return result, 0
}
func (h Controller) FreshLeaveFightLevel(data *fight.FRESH_LEAVE_FIGHT_LEVEL, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
defer c.GetSpace().EnterMap(c)
out := info.NewOutInfo()
copier.CopyWithOption(out, c.GetInfo(), copier.Option{DeepCopy: true})
c.SendPackCmd(2001, out)
return result, 0
}
func (h Controller) PetTawor(data *fight.StartTwarInboundInfo, c *player.Player) (result *fight.S2C_ChoiceLevelRequestInfo, err errorcode.ErrorCode) {
if !c.CanFight() {
return nil, errorcode.ErrorCodes.ErrSystemError
}
c.Fightinfo.Mode = fightinfo.BattleMode.MULTI_MODE
c.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
monsterInfo := &model.PlayerInfo{}
var boss *configmodel.BaseTowerConfig
var next *configmodel.BaseTowerConfig
result = &fight.S2C_ChoiceLevelRequestInfo{}
switch data.Head.CMD {
case 2429: //试炼之塔
boss = service.NewTower600Service().Boss(c.Info.CurrentFreshStage)
next = service.NewTower600Service().Boss(c.Info.CurrentFreshStage + 1)
result.CurFightLevel = uint32(c.Info.CurrentFreshStage)
case 2415: //勇者之塔
boss = service.NewTower500Service().Boss(c.Info.CurrentStage)
next = service.NewTower500Service().Boss(c.Info.CurrentStage + 1)
result.CurFightLevel = uint32(c.Info.CurrentStage)
case 2425:
boss = service.NewTower110Service().Boss(c.CurDark)
}
if next != nil {
for _, v := range next.BossIds {
r := service.NewBossService().Get(v)
result.BossID = append(result.BossID, uint32(r.MonID))
}
}
for i, v := range boss.BossIds {
r := service.NewBossService().Get(v)
if r != nil {
monster := model.GenPetInfo(int(r.MonID), 24, int(r.Nature), 0, int(r.Lv), nil)
if r.Hp != 0 {
monster.Hp = uint32(r.Hp)
monster.MaxHp = uint32(r.Hp)
}
for i, v := range r.Prop {
if v != 0 {
monster.Prop[i] = v
}
}
if len(r.SKill) != 0 {
for i := 0; i < len(monster.SkillList); i++ {
if r.SKill[i] != 0 {
monster.SkillList[i].ID = r.SKill[i]
}
}
}
if len(r.Effect) != 0 {
for _, v := range r.Effect {
EID, args := service.NewEffectService().Args(v)
monster.EffectInfo = append(monster.EffectInfo, model.PetEffectInfo{
Idx: uint16(v),
EID: gconv.Uint16(EID),
Args: gconv.Ints(args),
})
}
}
monster.CatchTime = uint32(i)
monsterInfo.PetList = append(monsterInfo.PetList, *monster)
}
}
ai := player.NewAI_player(monsterInfo)
_, err = fight.NewFight(c, ai, func(foi fightinfo.FightOverInfo) {
if foi.Reason == 0 && foi.WinnerId == c.Info.UserID { //我放获胜
switch data.Head.CMD {
case 2429: //试炼之塔
c.TawerCompletedTask(600, int(c.Info.CurrentFreshStage))
c.Info.CurrentFreshStage++
if c.Info.CurrentFreshStage >= c.Info.MaxFreshStage {
c.Info.MaxFreshStage = c.Info.CurrentFreshStage
}
case 2415: //勇者之塔
c.TawerCompletedTask(500, int(c.Info.CurrentStage))
c.Info.CurrentStage++
if c.Info.CurrentStage >= c.Info.MaxStage {
c.Info.MaxStage = c.Info.CurrentStage
}
case 2425:
c.TawerCompletedTask(110, int(c.CurDark))
}
}
}) ///开始对战,房主方以及被邀请方
return
}

View File

@@ -0,0 +1,79 @@
package controller
import (
"blazing/modules/player/model"
"blazing/logic/service/fight"
"blazing/logic/service/player"
)
// dispatchFightActionEnvelope 把控制器层收到的统一动作结构分发回现有 FightI 接口。
func (h Controller) dispatchFightActionEnvelope(c *player.Player, envelope fight.FightActionEnvelope) {
if c == nil || c.FightC == nil {
return
}
switch envelope.ActionType {
case fight.FightActionTypeSkill:
go c.FightC.UseSkillAt(c, envelope.SkillID, envelope.ActorIndex, envelope.EncodedTargetIndex())
case fight.FightActionTypeItem:
go c.FightC.UseItemAt(c, envelope.CatchTime, envelope.ItemID, envelope.ActorIndex, envelope.EncodedTargetIndex())
case fight.FightActionTypeChange:
go c.FightC.ChangePetAt(c, envelope.CatchTime, envelope.ActorIndex)
case fight.FightActionTypeEscape:
go c.FightC.Over(c, model.BattleOverReason.PlayerEscape)
case fight.FightActionTypeChat:
go c.FightC.Chat(c, envelope.Chat)
}
}
// buildLegacyUseSkillEnvelope 把旧 2405 技能包映射成统一动作结构。
func buildLegacyUseSkillEnvelope(data *UseSkillInInfo) fight.FightActionEnvelope {
if data == nil {
return fight.NewSkillActionEnvelope(0, 0, 0, fight.SkillTargetOpponent, 0)
}
return fight.NewSkillActionEnvelope(data.SkillId, 0, 0, fight.SkillTargetOpponent, 0)
}
// buildIndexedUseSkillEnvelope 把 7505 多战位技能包映射成统一动作结构。
func buildIndexedUseSkillEnvelope(data *UseSkillAtInboundInfo) fight.FightActionEnvelope {
if data == nil {
return fight.NewSkillActionEnvelope(0, 0, 0, fight.SkillTargetOpponent, 0)
}
return fight.NewSkillActionEnvelope(
data.SkillId,
int(data.ActorIndex),
int(data.TargetIndex),
data.TargetRelation,
data.AtkType,
)
}
// buildLegacyUseItemEnvelope 把旧 2406 道具包映射成统一动作结构。
func buildLegacyUseItemEnvelope(data *UsePetItemInboundInfo) fight.FightActionEnvelope {
if data == nil {
return fight.NewItemActionEnvelope(0, 0, 0, 0, fight.SkillTargetOpponent)
}
return fight.NewItemActionEnvelope(data.CatchTime, data.ItemId, 0, 0, fight.SkillTargetOpponent)
}
// buildLegacyChangeEnvelope 把旧 2407 切宠包映射成统一动作结构。
func buildLegacyChangeEnvelope(data *ChangePetInboundInfo) fight.FightActionEnvelope {
if data == nil {
return fight.NewChangeActionEnvelope(0, 0)
}
return fight.NewChangeActionEnvelope(data.CatchTime, 0)
}
// buildLegacyEscapeEnvelope 构造旧 2410 逃跑包对应的统一动作结构。
func buildLegacyEscapeEnvelope() fight.FightActionEnvelope {
return fight.NewEscapeActionEnvelope()
}
// buildChatEnvelope 把战斗聊天包映射成统一动作结构。
func buildChatEnvelope(data *ChatInfo) fight.FightActionEnvelope {
if data == nil {
return fight.NewChatActionEnvelope("")
}
return fight.NewChatActionEnvelope(data.Message)
}

View File

@@ -0,0 +1,258 @@
package controller
import (
"blazing/common/socket/errorcode"
"blazing/common/utils"
"blazing/logic/service/fight"
fightinfo "blazing/logic/service/fight/info"
"blazing/logic/service/player"
"blazing/logic/service/space/info"
configmodel "blazing/modules/config/model"
"blazing/modules/config/service"
"blazing/modules/player/model"
"sync/atomic"
"github.com/gogf/gf/v2/util/gconv"
"github.com/jinzhu/copier"
)
const (
towerCmdChoiceTrial uint32 = 2428
towerCmdChoiceBrave uint32 = 2414
towerCmdFightTrial uint32 = 2429
towerCmdFightBrave uint32 = 2415
towerCmdFightDark uint32 = 2425
towerTaskDark int = 110
towerTaskBrave int = 500
towerTaskTrial int = 600
)
type towerChoiceState struct {
currentLevel *uint32
maxLevel *uint32
service *service.TowerService
}
// 暗黑门进入boss
func (h Controller) FreshOpen(data *C2S_OPEN_DARKPORTAL, c *player.Player) (result *fight.S2C_OPEN_DARKPORTAL, err errorcode.ErrorCode) {
result = &fight.S2C_OPEN_DARKPORTAL{}
towerBosses := service.NewTower110Service().Boss(uint32(data.Level))
bossConfig, ok := firstTowerBossConfig(towerBosses)
if !ok {
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
}
bosses := service.NewBossService().Get(bossConfig.BossIds[0])
if len(bosses) == 0 {
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
}
result.CurBossID = uint32(bosses[0].MonID)
c.CurDark = uint32(data.Level)
defer c.GetSpace().LeaveMap(c)
return result, 0
}
// FreshChoiceFightLevel 处理玩家选择挑战模式(试炼之塔或勇者之塔)
func (h Controller) FreshChoiceFightLevel(data *C2S_FRESH_CHOICE_FIGHT_LEVEL, c *player.Player) (result *fight.S2C_FreshChoiceLevelRequestInfo, err errorcode.ErrorCode) {
result = &fight.S2C_FreshChoiceLevelRequestInfo{}
c.Info.CurrentFreshStage = utils.Max(c.Info.CurrentFreshStage, 1)
c.Info.CurrentStage = utils.Max(c.Info.CurrentStage, 1)
choiceState, ok := towerChoiceRuntime(c, data.Head.CMD)
if !ok {
return nil, errorcode.ErrorCodes.ErrSystemError
}
if data.Level > 0 {
if !canSelectTowerLevel(data.Level, *choiceState.maxLevel) {
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
}
*choiceState.currentLevel = uint32(data.Level)
}
result.CurFightLevel = *choiceState.currentLevel
appendTowerBossPreview(&result.BossId, choiceState.service.Boss(*choiceState.currentLevel))
atomic.StoreUint32(&c.Canmon, 0)
defer c.GetSpace().LeaveMap(c)
return result, 0
}
// FreshLeaveFightLevel 处理控制器请求。
func (h Controller) FreshLeaveFightLevel(data *FRESH_LEAVE_FIGHT_LEVEL, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
_ = data
defer c.GetSpace().EnterMap(c)
out := info.NewOutInfo()
copier.CopyWithOption(out, c.GetInfo(), copier.Option{DeepCopy: true})
return result, 0
}
// PetTawor 处理控制器请求。
func (h Controller) PetTawor(data *StartTwarInboundInfo, c *player.Player) (result *fight.S2C_ChoiceLevelRequestInfo, err errorcode.ErrorCode) {
if err = c.CanFight(); err != 0 {
return nil, err
}
bossList, currentLevel, taskID, ok := towerFightBosses(c, data.Head.CMD)
if !ok {
return nil, errorcode.ErrorCodes.ErrSystemError
}
currentBoss, ok := firstTowerBossConfig(bossList)
if !ok {
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
}
result = &fight.S2C_ChoiceLevelRequestInfo{CurFightLevel: currentLevel}
appendTowerNextBossPreview(&result.BossID, bossList)
monsterInfo, bossScript, ok := buildTowerMonsterInfo(currentBoss)
if !ok {
return nil, errorcode.ErrorCodes.ErrPokemonNotExists
}
c.Fightinfo.Mode = fightinfo.BattleMode.MULTI_MODE
c.Fightinfo.Status = fightinfo.BattleMode.FIGHT_WITH_NPC
ai := player.NewAI_player(monsterInfo)
ai.BossScript = bossScript
_, err = fight.NewFight(c, ai, c.GetPetInfo(100), ai.GetPetInfo(0), func(foi model.FightOverInfo) {
if foi.Reason != 0 || foi.WinnerId != c.Info.UserID {
return
}
handleTowerFightWin(c, data.Head.CMD, taskID, currentLevel)
})
return
}
func towerChoiceRuntime(c *player.Player, cmd uint32) (towerChoiceState, bool) {
switch cmd {
case towerCmdChoiceTrial:
return towerChoiceState{
currentLevel: &c.Info.CurrentFreshStage,
maxLevel: &c.Info.MaxFreshStage,
service: service.NewTower600Service(),
}, true
case towerCmdChoiceBrave:
return towerChoiceState{
currentLevel: &c.Info.CurrentStage,
maxLevel: &c.Info.MaxStage,
service: service.NewTower500Service(),
}, true
default:
return towerChoiceState{}, false
}
}
func towerFightBosses(c *player.Player, cmd uint32) ([]configmodel.BaseTowerConfig, uint32, int, bool) {
switch cmd {
case towerCmdFightTrial:
return service.NewTower600Service().Boss(c.Info.CurrentFreshStage, c.Info.CurrentFreshStage+1), c.Info.CurrentFreshStage, towerTaskTrial, true
case towerCmdFightBrave:
return service.NewTower500Service().Boss(c.Info.CurrentStage, c.Info.CurrentStage+1), c.Info.CurrentStage, towerTaskBrave, true
case towerCmdFightDark:
return service.NewTower110Service().Boss(c.CurDark), c.CurDark, towerTaskDark, true
default:
return nil, 0, 0, false
}
}
func canSelectTowerLevel(targetLevel uint, maxLevel uint32) bool {
return targetLevel == 1 || targetLevel <= uint(maxLevel)
}
func firstTowerBossConfig(bossList []configmodel.BaseTowerConfig) (configmodel.BaseTowerConfig, bool) {
if len(bossList) == 0 || len(bossList[0].BossIds) == 0 {
return configmodel.BaseTowerConfig{}, false
}
return bossList[0], true
}
func appendTowerBossPreview(dst *[]uint32, bossList []configmodel.BaseTowerConfig) {
bossConfig, ok := firstTowerBossConfig(bossList)
if !ok {
return
}
bosses := service.NewBossService().Get(bossConfig.BossIds[0])
for _, boss := range bosses {
*dst = append(*dst, uint32(boss.MonID))
}
}
func appendTowerNextBossPreview(dst *[]uint32, bossList []configmodel.BaseTowerConfig) {
if len(bossList) < 2 || len(bossList[1].BossIds) == 0 {
return
}
bosses := service.NewBossService().Get(bossList[1].BossIds[0])
for _, boss := range bosses {
*dst = append(*dst, uint32(boss.MonID))
}
}
func buildTowerMonsterInfo(towerBoss configmodel.BaseTowerConfig) (*model.PlayerInfo, string, bool) {
bosses := service.NewBossService().Get(towerBoss.BossIds[0])
if len(bosses) == 0 {
return nil, "", false
}
monsterInfo := &model.PlayerInfo{Nick: towerBoss.Name}
for i, boss := range bosses {
monster := model.GenPetInfo(int(boss.MonID), 24, int(boss.Nature), 0, int(boss.Lv), nil, 0)
if boss.Hp != 0 {
monster.Hp = uint32(boss.Hp)
monster.MaxHp = uint32(boss.Hp)
}
for statIdx, prop := range boss.Prop {
if prop != 0 {
monster.Prop[statIdx] = prop
}
}
for skillIdx := 0; skillIdx < len(monster.SkillList) && skillIdx < len(boss.SKill); skillIdx++ {
if boss.SKill[skillIdx] != 0 {
monster.SkillList[skillIdx].ID = boss.SKill[skillIdx]
}
}
effects := service.NewEffectService().Args(boss.Effect)
for _, effect := range effects {
monster.EffectInfo = append(monster.EffectInfo, model.PetEffectInfo{
Idx: uint16(effect.ID),
EID: gconv.Uint16(effect.Eid),
Args: gconv.Ints(effect.Args),
})
}
monster.CatchTime = uint32(i)
monsterInfo.PetList = append(monsterInfo.PetList, *monster)
}
return monsterInfo, bosses[0].Script, true
}
func handleTowerFightWin(c *player.Player, cmd uint32, taskID int, currentLevel uint32) {
c.TawerCompletedTask(taskID, int(currentLevel))
switch cmd {
case towerCmdFightTrial:
c.Info.CurrentFreshStage++
if c.Info.CurrentFreshStage >= c.Info.MaxFreshStage {
c.Info.MaxFreshStage = c.Info.CurrentFreshStage
}
case towerCmdFightBrave:
c.Info.CurrentStage++
if c.Info.CurrentStage >= c.Info.MaxStage {
c.Info.MaxStage = c.Info.CurrentStage
}
}
}

View File

@@ -0,0 +1,65 @@
package controller
import (
"blazing/common/rpc"
"blazing/common/socket/errorcode"
"blazing/logic/service/common"
"blazing/logic/service/fight"
"blazing/logic/service/fight/pvp"
"blazing/logic/service/player"
)
// 表示"宠物王加入"的入站消息数据
type PetTOPLEVELnboundInfo struct {
Head common.TomeeHeader `cmd:"2458" struc:"skip"`
Mode uint32 //巅峰赛对战模式 19 = 普通模式单精灵 20 = 普通模式多精灵
}
// JoINtop 处理控制器请求。
func (h Controller) JoINtop(data *PetTOPLEVELnboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
err = pvp.JoinPeakQueue(c, data.Mode)
if err != 0 {
return nil, err
}
if Maincontroller.RPCClient == nil || Maincontroller.RPCClient.MatchJoinOrUpdate == nil {
pvp.CancelPeakQueue(c)
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
}
fightMode, status, err := pvp.NormalizePeakMode(data.Mode)
if err != 0 {
pvp.CancelPeakQueue(c)
return nil, err
}
joinPayload := rpc.PVPMatchJoinPayload{
RuntimeServerID: h.UID,
UserID: c.Info.UserID,
Nick: c.Info.Nick,
FightMode: fightMode,
Status: status,
CatchTimes: pvp.AvailableCatchTimes(c.GetPetInfo(0)),
}
if callErr := Maincontroller.RPCClient.MatchJoinOrUpdate(joinPayload); callErr != nil {
pvp.CancelPeakQueue(c)
return nil, errorcode.ErrorCodes.ErrSystemBusyTryLater
}
return nil, -1
}
// CancelPeakQueue 处理控制器请求。
func (h Controller) CancelPeakQueue(data *PeakQueueCancelInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if Maincontroller.RPCClient != nil && Maincontroller.RPCClient.MatchCancel != nil {
_ = Maincontroller.RPCClient.MatchCancel(c.Info.UserID)
}
pvp.CancelPeakQueue(c)
return nil, -1
}
// SubmitPeakBanPick 处理控制器请求。
func (h Controller) SubmitPeakBanPick(data *PeakBanPickSubmitInboundInfo, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
err = pvp.SubmitBanPick(c, data.SelectedCatchTimes, data.BanCatchTimes)
if err != 0 {
return nil, err
}
return nil, -1
}

View File

@@ -2,24 +2,27 @@ package controller
import (
"blazing/common/socket/errorcode"
"blazing/modules/player/model"
"sync/atomic"
"blazing/logic/service/common"
"blazing/logic/service/fight"
"blazing/logic/service/fight/info"
"blazing/logic/service/player"
"blazing/logic/service/space"
)
// ArenaSetOwner 处理玩家占据擂台的请求
// public static const ARENA_SET_OWENR:uint = 2417;
// 如果星际擂台上无人,站到星际擂台的包
// 前端到后端无数据内容 空包
// 后端到前端无数据内容 空包
// ArenaSetOwner 都需要通过2419包广播更新擂台状态
func (h Controller) ArenaSetOwner(data *fight.ARENA_SET_OWENR, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
// ARENA_SET_OWENR 定义请求或响应数据结构。
type ARENA_SET_OWENR struct {
Head common.TomeeHeader `cmd:"2417" struc:"skip"`
}
if !c.CanFight() {
return nil, errorcode.ErrorCodes.ErrPokemonNotEligible
// ArenaSetOwner 处理玩家占据擂台的请求
// ArenaSetOwner 都需要通过2419包广播更新擂台状态
func (h Controller) ArenaSetOwner(data *ARENA_SET_OWENR, c *player.Player) (result *struct{}, err errorcode.ErrorCode) {
r := c.CanFight()
if r != 0 {
return nil, r
}
c.Fightinfo.Mode = 0 //取消队列匹配
if atomic.CompareAndSwapUint32(&c.GetSpace().Owner.Flag, 0, 1) {
@@ -33,18 +36,22 @@ func (h Controller) ArenaSetOwner(data *fight.ARENA_SET_OWENR, c *player.Player)
return nil, errorcode.ErrorCodes.ErrChampionExists
}
// ARENA_FIGHT_OWENR 定义请求或响应数据结构。
type ARENA_FIGHT_OWENR struct {
Head common.TomeeHeader `cmd:"2418" struc:"skip"`
}
// ArenaFightOwner 挑战擂台的包
// 前端到后端无数据内容 空包
// 后端到前端无数据内容 空包
// 还是后端主动发送2503的包给双方前端后 等待前端加载完毕 主动发送2404包通知后端开始战斗
// ArenaFightOwner 并不会通知对方是否接受挑战。只要有人挑战就直接进入对战
func (h Controller) ArenaFightOwner(data *fight.ARENA_FIGHT_OWENR, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
func (h Controller) ArenaFightOwner(data1 *ARENA_FIGHT_OWENR, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
if !c.CanFight() {
return nil, errorcode.ErrorCodes.ErrNoEligiblePokemon
r := c.CanFight()
if r != 0 {
return nil, r
}
if c.Info.UserID == c.GetSpace().Owner.UserID {
if c.IsArenaHost() {
return nil, errorcode.ErrorCodes.ErrNoEligiblePokemon
}
@@ -52,7 +59,7 @@ func (h Controller) ArenaFightOwner(data *fight.ARENA_FIGHT_OWENR, c *player.Pla
return nil, errorcode.ErrorCodes.ErrSystemError200007
}
if !c.GetSpace().Owner.ARENA_Player.CanFight() {
if c.GetSpace().Owner.ARENA_Player.CanFight() != 0 {
c.GetSpace().Owner.Set(c)
c.GetSpace().Broadcast(c, 2419, &c.GetSpace().Owner)
c.SendPackCmd(2419, &c.GetSpace().Owner)
@@ -64,7 +71,7 @@ func (h Controller) ArenaFightOwner(data *fight.ARENA_FIGHT_OWENR, c *player.Pla
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, c.Info.PetList, c.GetSpace().Owner.ARENA_Player.GetInfo().PetList, func(foi model.FightOverInfo) { //我方邀请擂主挑战,我方先手
if foi.Reason != 0 && foi.WinnerId == c.GetInfo().UserID { //异常退出
@@ -75,12 +82,36 @@ func (h Controller) ArenaFightOwner(data *fight.ARENA_FIGHT_OWENR, c *player.Pla
}
if foi.Reason == 0 { //异常退出
if foi.Reason == 0 { //正常获胜
// addev := int64(int(1) * int(cool.Connected) * int(c.GetSpace().Owner.HostWins) * (int(c.GetSpace().User.Count()) / int(cool.Connected)))
addev := int64(int(2) * int(c.GetSpace().Owner.HostWins) * (int(c.GetSpace().User.Count())))
if foi.WinnerId == c.GetInfo().UserID {
c.Info.MaxArenaWins += 1
if addev != 0 {
c.Info.EVPool += addev
rewards := &info.S2C_GET_BOSS_MONSTER{}
rewards.AddItem(9, uint32(addev))
c.SendPackCmd(8004, rewards) //发送EV
}
} else {
c.GetSpace().Owner.ARENA_Player.GetInfo().MaxArenaWins += 1
oper := c.GetSpace().Owner.ARENA_Player
if oper != nil {
if oper.GetInfo() != nil {
c.GetSpace().Owner.ARENA_Player.GetInfo().MaxArenaWins += 1
if addev != 0 {
c.GetSpace().Owner.ARENA_Player.GetInfo().EVPool += addev
rewards := &info.S2C_GET_BOSS_MONSTER{}
rewards.AddItem(9, uint32(addev))
c.GetSpace().Owner.ARENA_Player.SendPackCmd(8004, rewards) //发送EV
}
}
}
}
}
@@ -108,17 +139,15 @@ func (h Controller) ArenaFightOwner(data *fight.ARENA_FIGHT_OWENR, c *player.Pla
// ArenaGetInfo 获取星际擂台信息的包 进入空间站地图前端会发送请求包 或者 有人站到星际擂台上后 广播回包
// 前端到后端无数据内容
// ArenaGetInfo 后端到前端
func (h Controller) ArenaGetInfo(data *fight.ARENA_GET_INFO, c *player.Player) (result *space.ARENA, err errorcode.ErrorCode) {
func (h Controller) ArenaGetInfo(data *ARENA_GET_INFO, c *player.Player) (result *space.ARENA, err errorcode.ErrorCode) {
result = &c.GetSpace().Owner
return
}
// ArenaUpfight 放弃擂台挑战的包
// 前端到后端无数据内容
// 后端到前端无数据内容
// ArenaUpfight 都需要通过2419包广播更新擂台状态
func (h Controller) ArenaUpfight(data *fight.ARENA_UPFIGHT, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
func (h Controller) ArenaUpfight(data *ARENA_UPFIGHT, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
//原子操作,修改擂台状态
if atomic.LoadUint32(&c.GetSpace().Owner.UserID) != c.GetInfo().UserID { //说明已经有人了
return nil, errorcode.ErrorCodes.ErrChampionCannotCancel
@@ -140,7 +169,7 @@ func (h Controller) ArenaUpfight(data *fight.ARENA_UPFIGHT, c *player.Player) (r
// 后端到前端无数据内容
// public static const ARENA_OWENR_OUT:uint = 2423;
// ArenaOwnerAcce 此包不清楚具体怎么触发 但已知此包为后端主动发送。不清楚什么情况下回用到
func (h Controller) ArenaOwnerAcce(data *fight.ARENA_OWENR_ACCE, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
func (h Controller) ArenaOwnerAcce(data *ARENA_OWENR_ACCE, c *player.Player) (result *fight.NullOutboundInfo, err errorcode.ErrorCode) {
s := c.GetSpace()

View File

@@ -0,0 +1,198 @@
package controller
import "blazing/logic/service/common"
// FightNpcMonsterInboundInfo 定义请求或响应数据结构。
type FightNpcMonsterInboundInfo struct {
Head common.TomeeHeader `cmd:"2408" struc:"skip"`
Number uint32 `fieldDesc:"地图刷新怪物结构体对应的序号 1 - 9 的位置序号" `
}
// ChallengeBossInboundInfo 定义请求或响应数据结构。
type ChallengeBossInboundInfo struct {
Head common.TomeeHeader `cmd:"2411" struc:"skip"`
BossId uint32 `json:"bossId"`
}
// ReadyToFightInboundInfo 定义请求或响应数据结构。
type ReadyToFightInboundInfo struct {
Head common.TomeeHeader `cmd:"2404" struc:"skip"`
}
// GroupReadyFightFinishInboundInfo 旧组队协议准备完成。
type GroupReadyFightFinishInboundInfo struct {
Head common.TomeeHeader `cmd:"7556" struc:"skip"`
}
type GroupUseSkillInboundInfo struct {
Head common.TomeeHeader `cmd:"7558" struc:"skip"`
ActorIndex uint8
TargetSide uint8
TargetPos uint8
SkillId uint32
}
type GroupUseItemInboundInfo struct {
Head common.TomeeHeader `cmd:"7562" struc:"skip"`
ActorIndex uint8
ItemId uint32
}
type GroupChangePetInboundInfo struct {
Head common.TomeeHeader `cmd:"7563" struc:"skip"`
ActorIndex uint8
CatchTime uint32
}
type GroupEscapeInboundInfo struct {
Head common.TomeeHeader `cmd:"7565" struc:"skip"`
ActorIndex uint8
}
type GroupFightWinCloseInboundInfo struct {
Head common.TomeeHeader `cmd:"7574" struc:"skip"`
}
type GroupFightTimeoutExitInboundInfo struct {
Head common.TomeeHeader `cmd:"7587" struc:"skip"`
}
// EscapeFightInboundInfo 定义请求或响应数据结构。
type EscapeFightInboundInfo struct {
Head common.TomeeHeader `cmd:"2410" struc:"skip"`
}
// StartPetWarInboundInfo 定义请求或响应数据结构。
type StartPetWarInboundInfo struct {
Head common.TomeeHeader `cmd:"2431" struc:"skip"`
}
// StartTwarInboundInfo 定义请求或响应数据结构。
type StartTwarInboundInfo struct {
Head common.TomeeHeader `cmd:"2429|2415|2425" struc:"skip"`
}
// ARENA_GET_INFO 定义请求或响应数据结构。
type ARENA_GET_INFO struct {
Head common.TomeeHeader `cmd:"2419" struc:"skip"`
}
// ARENA_UPFIGHT 定义请求或响应数据结构。
type ARENA_UPFIGHT struct {
Head common.TomeeHeader `cmd:"2420" struc:"skip"`
}
// ARENA_OWENR_ACCE 定义请求或响应数据结构。
type ARENA_OWENR_ACCE struct {
Head common.TomeeHeader `cmd:"2422" struc:"skip"`
}
// PetKingJoinInboundInfo 定义请求或响应数据结构。
type PetKingJoinInboundInfo struct {
Head common.TomeeHeader `cmd:"2413" struc:"skip"`
Type uint32
FightType uint32
}
// PeakQueueCancelInboundInfo 定义请求或响应数据结构。
type PeakQueueCancelInboundInfo struct {
Head common.TomeeHeader `cmd:"2459" struc:"skip"`
}
// PeakBanPickSubmitInboundInfo 定义请求或响应数据结构。
type PeakBanPickSubmitInboundInfo struct {
Head common.TomeeHeader `cmd:"2460" struc:"skip"`
SelectedCatchTimesLen uint32 `struc:"sizeof=SelectedCatchTimes"`
SelectedCatchTimes []uint32 `json:"selectedCatchTimes"`
BanCatchTimesLen uint32 `struc:"sizeof=BanCatchTimes"`
BanCatchTimes []uint32 `json:"banCatchTimes"`
}
// HandleFightInviteInboundInfo 定义请求或响应数据结构。
type HandleFightInviteInboundInfo struct {
Head common.TomeeHeader `cmd:"2403" struc:"skip"`
UserID uint32 `json:"userId" codec:"userId,uint"`
Flag uint32 `json:"flag" codec:"flag,uint"`
Mode uint32 `json:"mode" codec:"mode,uint"`
}
// InviteToFightInboundInfo 定义请求或响应数据结构。
type InviteToFightInboundInfo struct {
Head common.TomeeHeader `cmd:"2401" struc:"skip"`
UserID uint32
Mode uint32
}
// InviteFightCancelInboundInfo 定义请求或响应数据结构。
type InviteFightCancelInboundInfo struct {
Head common.TomeeHeader `cmd:"2402" struc:"skip"`
}
// UseSkillInInfo 定义请求或响应数据结构。
type UseSkillInInfo struct {
Head common.TomeeHeader `cmd:"2405" struc:"skip"`
SkillId uint32
}
// UseSkillAtInboundInfo 定义请求或响应数据结构。
type UseSkillAtInboundInfo struct {
Head common.TomeeHeader `cmd:"7505" struc:"skip"`
SkillId uint32 `json:"skillId"`
ActorIndex uint8 `json:"actorIndex"`
TargetIndex uint8 `json:"targetIndex"`
TargetRelation uint8 `json:"targetRelation"`
AtkType uint8 `json:"atkType"`
}
// ChangePetInboundInfo 定义请求或响应数据结构。
type ChangePetInboundInfo struct {
Head common.TomeeHeader `cmd:"2407" struc:"skip"`
CatchTime uint32 `json:"catchTime"`
}
// CatchMonsterInboundInfo 定义请求或响应数据结构。
type CatchMonsterInboundInfo struct {
Head common.TomeeHeader `cmd:"2409" struc:"skip"`
CapsuleId uint32 `json:"capsuleId" fieldDescription:"胶囊id" uint:"true"`
}
// LoadPercentInboundInfo 定义请求或响应数据结构。
type LoadPercentInboundInfo struct {
Head common.TomeeHeader `cmd:"2441" struc:"skip"`
Percent uint32 `fieldDescription:"加载百分比"`
}
// UsePetItemInboundInfo 定义请求或响应数据结构。
type UsePetItemInboundInfo struct {
Head common.TomeeHeader `cmd:"2406" struc:"skip"`
CatchTime uint32 `description:"精灵捕获时间" codec:"catchTime"`
ItemId uint32 `description:"使用的物品ID" codec:"itemId"`
Reversed1 uint32 `description:"填充字段 0" codec:"reversed1"`
}
// ChatInfo 定义请求或响应数据结构。
type ChatInfo struct {
Head common.TomeeHeader `cmd:"50002" struc:"skip"`
Reserve uint32 `json:"reserve" fieldDescription:"填充 默认值为0" uint:"true"`
MessageLen uint32 `struc:"sizeof=Message"`
Message string `json:"message" fieldDescription:"消息内容, 结束符为utf-8的数字0"`
}
// C2S_FRESH_CHOICE_FIGHT_LEVEL 定义请求或响应数据结构。
type C2S_FRESH_CHOICE_FIGHT_LEVEL struct {
Head common.TomeeHeader `cmd:"2428|2414" struc:"skip"`
Level uint `json:"level"`
}
// C2S_OPEN_DARKPORTAL 定义请求或响应数据结构。
type C2S_OPEN_DARKPORTAL struct {
Head common.TomeeHeader `cmd:"2424" struc:"skip"`
Level uint32 `json:"level"`
}
// FRESH_LEAVE_FIGHT_LEVEL 定义请求或响应数据结构。
type FRESH_LEAVE_FIGHT_LEVEL struct {
Head common.TomeeHeader `cmd:"2430|2416|2426" struc:"skip"`
}

View File

@@ -0,0 +1,61 @@
package controller
import "blazing/logic/service/common"
// SeeOnlineInboundInfo 定义请求或响应数据结构。
type SeeOnlineInboundInfo struct {
Head common.TomeeHeader `cmd:"2157" struc:"skip"`
UserIdsLen uint32 `json:"userIdsLen" struc:"sizeof=UserIds"`
UserIds []uint32 `json:"userIds" `
}
// FriendAddInboundInfo 定义请求或响应数据结构。
type FriendAddInboundInfo struct {
Head common.TomeeHeader `cmd:"2151" struc:"skip"`
UserID uint32 `json:"userID"`
}
// FriendAnswerInboundInfo 定义请求或响应数据结构。
type FriendAnswerInboundInfo struct {
Head common.TomeeHeader `cmd:"2152" struc:"skip"`
UserID uint32 `json:"userID"`
Flag uint32 `json:"flag"`
}
// FriendRemoveInboundInfo 定义请求或响应数据结构。
type FriendRemoveInboundInfo struct {
Head common.TomeeHeader `cmd:"2153" struc:"skip"`
UserID uint32 `json:"userID"`
}
// AcceptTaskInboundInfo 定义请求或响应数据结构。
type AcceptTaskInboundInfo struct {
Head common.TomeeHeader `cmd:"2201|2231" struc:"skip"`
TaskId uint32 `json:"taskId" description:"任务ID"`
}
// AddTaskBufInboundInfo 定义请求或响应数据结构。
type AddTaskBufInboundInfo struct {
Head common.TomeeHeader `cmd:"2204|2235" struc:"skip"`
TaskId uint32 `json:"taskId" description:"任务ID"`
TaskList []uint32 `struc:"[20]byte"`
}
// CompleteTaskInboundInfo 定义请求或响应数据结构。
type CompleteTaskInboundInfo struct {
Head common.TomeeHeader `cmd:"2202|2233" struc:"skip"`
TaskId uint32 `json:"taskId" description:"任务ID"`
OutState uint32 `json:"outState" `
}
// GetTaskBufInboundInfo 定义请求或响应数据结构。
type GetTaskBufInboundInfo struct {
Head common.TomeeHeader `cmd:"2203|2234" struc:"skip"`
TaskId uint32 `json:"taskId" description:"任务ID"`
}
// DeleteTaskInboundInfo 定义请求或响应数据结构。
type DeleteTaskInboundInfo struct {
Head common.TomeeHeader `cmd:"2205|2232" struc:"skip"`
TaskId uint32 `json:"taskId" description:"任务ID"`
}

View File

@@ -0,0 +1,102 @@
package controller
import "blazing/logic/service/common"
// BuyInboundInfo 定义请求或响应数据结构。
type BuyInboundInfo struct {
Head common.TomeeHeader `cmd:"2601" struc:"skip"`
ItemId int64 `struc:"uint32"`
Count int64 `struc:"uint32"`
}
// BuyMultiInboundInfo 定义请求或响应数据结构。
type BuyMultiInboundInfo struct {
Head common.TomeeHeader `cmd:"2606" struc:"skip"`
ItemListLen uint32 `struc:"sizeof=ItemIds"`
ItemIds []uint32 `json:"itemIds" description:"购买的物品ID列表"`
}
// C2S_GOLD_BUY_PRODUCT 定义请求或响应数据结构。
type C2S_GOLD_BUY_PRODUCT struct {
Head common.TomeeHeader `cmd:"1104" struc:"skip"`
Type uint32 `json:"type"`
ProductID uint32 `json:"product_id"`
Count int64 `struc:"uint32"`
}
// ItemListInboundInfo 定义请求或响应数据结构。
type ItemListInboundInfo struct {
Head common.TomeeHeader `cmd:"2605|4475" struc:"skip"`
Param1 uint32
Param2 uint32
Param3 uint32
}
// GoldOnlineRemainInboundInfo 定义请求或响应数据结构。
type GoldOnlineRemainInboundInfo struct {
Head common.TomeeHeader `cmd:"1105|1106" struc:"skip"`
}
// ExpTotalRemainInboundInfo 定义请求或响应数据结构。
type ExpTotalRemainInboundInfo struct {
Head common.TomeeHeader `cmd:"2319" struc:"skip"`
}
// ChangePlayerClothInboundInfo 定义请求或响应数据结构。
type ChangePlayerClothInboundInfo struct {
Head common.TomeeHeader `cmd:"2604" struc:"skip"`
ClothesLen uint32 `struc:"sizeof=ClothList" fieldDesc:"穿戴装备的信息" json:"clothes_len"`
ClothList []uint32 `description:"玩家装备列表" codec:"list"`
}
// TalkCountInboundInfo 定义请求或响应数据结构。
type TalkCountInboundInfo struct {
Head common.TomeeHeader `cmd:"2701" struc:"skip"`
ID uint32 `description:"奖品的Type, 即ID" codec:"uint"`
}
// TalkCateInboundInfo 定义请求或响应数据结构。
type TalkCateInboundInfo struct {
Head common.TomeeHeader `cmd:"2702" struc:"skip"`
ID uint32 `description:"奖品的Type, 即ID" codec:"uint"`
}
// C2S_USE_PET_ITEM_OUT_OF_FIGHT 定义请求或响应数据结构。
type C2S_USE_PET_ITEM_OUT_OF_FIGHT struct {
Head common.TomeeHeader `cmd:"2326" struc:"skip"`
CatchTime uint32 `json:"catch_time"`
ItemID int32 `struc:"uint32"`
}
// C2S_PET_RESET_NATURE 定义请求或响应数据结构。
type C2S_PET_RESET_NATURE struct {
Head common.TomeeHeader `cmd:"2343" struc:"skip"`
CatchTime uint32
Nature uint32
ItemId uint32
}
// C2S_ITEM_SALE 定义请求或响应数据结构。
type C2S_ITEM_SALE struct {
Head common.TomeeHeader `cmd:"2602" struc:"skip"`
ItemId uint32
Amount uint32
}
// C2S_USE_SPEEDUP_ITEM 定义请求或响应数据结构。
type C2S_USE_SPEEDUP_ITEM struct {
Head common.TomeeHeader `cmd:"2327" struc:"skip"`
ItemID uint32
}
// C2S_USE_ENERGY_XISHOU 定义请求或响应数据结构。
type C2S_USE_ENERGY_XISHOU struct {
Head common.TomeeHeader `cmd:"2331" struc:"skip"`
ItemID uint32
}
// C2S_USE_AUTO_FIGHT_ITEM 定义请求或响应数据结构。
type C2S_USE_AUTO_FIGHT_ITEM struct {
Head common.TomeeHeader `cmd:"2329" struc:"skip"`
ItemID uint32
}

View File

@@ -0,0 +1,118 @@
package controller
import (
"blazing/logic/service/common"
"blazing/modules/player/model"
)
// EnterMapInboundInfo 定义请求或响应数据结构。
type EnterMapInboundInfo struct {
Head common.TomeeHeader `cmd:"2001" struc:"skip"`
MapType uint32
MapId uint32
Point model.Pos `fieldDesc:"直接给坐标xy"`
}
// GetMapHotInboundInfo 定义请求或响应数据结构。
type GetMapHotInboundInfo struct {
Head common.TomeeHeader `cmd:"1004" struc:"skip"`
}
// LeaveMapInboundInfo 定义请求或响应数据结构。
type LeaveMapInboundInfo struct {
Head common.TomeeHeader `cmd:"2002" struc:"skip"`
}
// ListMapPlayerInboundInfo 定义请求或响应数据结构。
type ListMapPlayerInboundInfo struct {
Head common.TomeeHeader `cmd:"2003" struc:"skip"`
}
// AttackBossInboundInfo 定义请求或响应数据结构。
type AttackBossInboundInfo struct {
Head common.TomeeHeader `cmd:"2412" struc:"skip"`
}
// WalkInInfo 定义请求或响应数据结构。
type WalkInInfo struct {
Head common.TomeeHeader `cmd:"2101" struc:"skip"`
Flag uint32
Point model.Pos `fieldDesc:"直接给坐标xy"`
PathLen uint32 `struc:"sizeof=Path"`
Path string
}
// FitmentUseringInboundInfo 定义请求或响应数据结构。
type FitmentUseringInboundInfo struct {
Head common.TomeeHeader `cmd:"10006" struc:"skip"`
TargetUserID uint32 `json:"targetUserId"`
}
// PetRoomListInboundInfo 定义请求或响应数据结构。
type PetRoomListInboundInfo struct {
Head common.TomeeHeader `cmd:"2324" struc:"skip"`
TargetUserID uint32 `json:"targetUserId"`
}
// FitmentAllInboundEmpty 定义请求或响应数据结构。
type FitmentAllInboundEmpty struct {
Head common.TomeeHeader `cmd:"10007" struc:"skip"`
}
// SET_FITMENT 定义请求或响应数据结构。
type SET_FITMENT struct {
Head common.TomeeHeader `cmd:"10008" struc:"skip"`
RoomID uint32 `json:"roomID"`
FitmentsLen uint32 `json:"fitmentsLen" struc:"sizeof=Fitments"`
Fitments []model.FitmentShowInfo `json:"usedList"`
}
// C2S_PetShowList 定义请求或响应数据结构。
type C2S_PetShowList struct {
CatchTime uint32 `json:"catchTime"`
PetID uint32 `json:"petID"`
}
// C2S_PET_ROOM_SHOW 定义请求或响应数据结构。
type C2S_PET_ROOM_SHOW struct {
Head common.TomeeHeader `cmd:"2323" struc:"skip"`
PetShowInfoLen uint32 `json:"PetShowInfoLen" struc:"sizeof=PetShowList"`
PetShowList []C2S_PetShowList `json:"PetShowList"`
}
// C2S_RoomPetInfo 定义请求或响应数据结构。
type C2S_RoomPetInfo struct {
Head common.TomeeHeader `cmd:"2325" struc:"skip"`
UserID uint32 `json:"userID"`
CatchTime uint32 `json:"catchTime"`
}
// C2S_BUY_FITMENT 定义请求或响应数据结构。
type C2S_BUY_FITMENT struct {
Head common.TomeeHeader `cmd:"10004" struc:"skip"`
ID uint32 `json:"id"`
Count uint32 `json:"count"`
}
// NonoInboundInfo 定义请求或响应数据结构。
type NonoInboundInfo struct {
Head common.TomeeHeader `cmd:"9003" struc:"skip"`
UserID uint32
}
// NonoFollowOrHomeInInfo 定义请求或响应数据结构。
type NonoFollowOrHomeInInfo struct {
Head common.TomeeHeader `cmd:"9019" struc:"skip"`
Flag uint32 `fieldDescription:"1为跟随 0为收回 且如果为收回 那么后续结构不需要发送" uint:"true"`
}
// SwitchFlyingInboundInfo 定义请求或响应数据结构。
type SwitchFlyingInboundInfo struct {
Head common.TomeeHeader `cmd:"2112" struc:"skip"`
Type uint32 `description:"开关, 0为取消飞行模式, 大于0为开启飞行模式" codec:"auto" uint:"true"`
}
// PetCureInboundInfo 定义请求或响应数据结构。
type PetCureInboundInfo struct {
Head common.TomeeHeader `cmd:"2306" struc:"skip"`
}

View File

@@ -0,0 +1,114 @@
package controller
import "blazing/logic/service/common"
// GetPetInfoInboundInfo 定义请求或响应数据结构。
type GetPetInfoInboundInfo struct {
Head common.TomeeHeader `cmd:"2301" struc:"skip"`
CatchTime uint32
}
// GetUserBagPetInfoInboundEmpty 定义请求或响应数据结构。
type GetUserBagPetInfoInboundEmpty struct {
Head common.TomeeHeader `cmd:"4483" struc:"skip"`
}
// SavePetBagOrderInboundInfo 定义请求或响应数据结构。
type SavePetBagOrderInboundInfo struct {
Head common.TomeeHeader `cmd:"4484" struc:"skip"`
PetListLen uint32 `struc:"int32,sizeof=PetList"`
PetList []uint32
BackupPetListLen uint32 `struc:"int32,sizeof=BackupPetList"`
BackupPetList []uint32
}
// PetReleaseInboundInfo 定义请求或响应数据结构。
type PetReleaseInboundInfo struct {
Head common.TomeeHeader `cmd:"2304" struc:"skip"`
CatchTime uint32
Flag uint32 `json:"flag" fieldDescription:"0为放入仓库1为放入背包" autoCodec:"true" uint:"true"`
}
// PetShowInboundInfo 定义请求或响应数据结构。
type PetShowInboundInfo struct {
Head common.TomeeHeader `cmd:"2305" struc:"skip"`
CatchTime uint32 `codec:"catchTime" inboundMessageType:"Pet_Show"`
Flag uint32 `codec:"flag"`
}
// PetOneCureInboundInfo 定义请求或响应数据结构。
type PetOneCureInboundInfo struct {
Head common.TomeeHeader `cmd:"2310" struc:"skip"`
CatchTime uint32 `json:"catchTime" fieldDescription:"精灵捕捉时间" uint:"true"`
}
// PET_ROWEI 定义请求或响应数据结构。
type PET_ROWEI struct {
Head common.TomeeHeader `cmd:"2321" struc:"skip"`
ID uint32
CatchTime uint32 `json:"catchTime" fieldDescription:"精灵捕捉时间" uint:"true"`
}
// PET_RETRIEVE 定义请求或响应数据结构。
type PET_RETRIEVE struct {
Head common.TomeeHeader `cmd:"2322" struc:"skip"`
CatchTime uint32 `json:"catchTime" fieldDescription:"精灵捕捉时间" uint:"true"`
}
// PetDefaultInboundInfo 定义请求或响应数据结构。
type PetDefaultInboundInfo struct {
Head common.TomeeHeader `cmd:"2308" struc:"skip"`
CatchTime uint32 `json:"catchTime" fieldDescription:"精灵捕捉时间" uint:"true" autoCodec:"true" inboundMessageType:"Pet_Default"`
}
// PetSetExpInboundInfo 定义请求或响应数据结构。
type PetSetExpInboundInfo struct {
Head common.TomeeHeader `cmd:"2318" struc:"skip"`
CatchTime uint32 `fieldDescription:"精灵获取时间" uint:"true" autoCodec:"true"`
Exp int64 `struc:"uint32"`
}
// PetBargeListInboundInfo 定义请求或响应数据结构。
type PetBargeListInboundInfo struct {
Head common.TomeeHeader `cmd:"2309" struc:"skip"`
StartPetId uint32 `description:"开始精灵id" codec:"startPetId"`
EndPetId uint32 `description:"结束精灵id" codec:"endPetId"`
}
// ChangeSkillInfo 定义请求或响应数据结构。
type ChangeSkillInfo struct {
Head common.TomeeHeader `cmd:"2312" struc:"skip"`
CatchTime uint32 `json:"catchTime"`
Reserved uint32 `json:"reserved"`
Reserved1 uint32 `json:"reserved1"`
HasSkill uint32 `json:"hasSkill"`
ReplaceSkill uint32 `json:"replaceSkill"`
}
// C2S_Skill_Sort 定义请求或响应数据结构。
type C2S_Skill_Sort struct {
Head common.TomeeHeader `cmd:"2328" struc:"skip"`
CapTm uint32 `json:"capTm"`
Skill [4]uint32 `json:"skill_1"`
}
// GetPetLearnableSkillsInboundInfo 查询当前精灵可学习技能含额外技能ExtSKill
type GetPetLearnableSkillsInboundInfo struct {
Head common.TomeeHeader `cmd:"52312" struc:"skip"`
CatchTime uint32 `json:"catchTime"`
}
type CommitPetSkillsInboundInfo struct {
Head common.TomeeHeader `cmd:"52313" struc:"skip"`
CatchTime uint32 `json:"catchTime"`
Skill [4]uint32 `json:"skill"`
}
type C2S_PetFusion struct {
Head common.TomeeHeader `cmd:"2351" struc:"skip"`
Mcatchtime uint32 `json:"mcatchtime" msgpack:"mcatchtime"`
Auxcatchtime uint32 `json:"auxcatchtime" msgpack:"auxcatchtime"`
Item1 [4]uint32 `json:"item1" msgpack:"item1"`
GoldItem1 [2]uint32 `json:"gold_item1" msgpack:"gold_item1"`
}

View File

@@ -0,0 +1,111 @@
package controller
import (
"blazing/cool"
"blazing/logic/service/common"
"blazing/modules/player/model"
"context"
"encoding/hex"
"fmt"
"hash/crc32"
)
// MAIN_LOGIN_IN 定义请求或响应数据结构。
type MAIN_LOGIN_IN struct {
Head common.TomeeHeader `cmd:"1001" struc:"skip"`
Sid []byte `struc:"[16]byte"`
}
// CheakSession 校验登录session。
func (l *MAIN_LOGIN_IN) CheakSession() (bool, uint32) {
t1 := hex.EncodeToString(l.Sid)
r, err := cool.CacheManager.Get(context.Background(), fmt.Sprintf("session:%d", l.Head.UserID))
if err != nil {
return false, 0
}
if r.String() != t1 {
return false, 0
}
crc32Table := crc32.MakeTable(crc32.IEEE)
crcValue := crc32.Checksum([]byte(l.Sid), crc32Table)
cool.CacheManager.Remove(context.Background(), fmt.Sprintf("session:%d", l.Head.UserID))
return true, crcValue
}
// SimUserInfoInboundInfo 定义请求或响应数据结构。
type SimUserInfoInboundInfo struct {
Head common.TomeeHeader `cmd:"2051" struc:"skip"`
UserId uint32 `fieldDescription:"米米号" uint:"true" codec:"true"`
}
// MoreUserInfoInboundInfo 定义请求或响应数据结构。
type MoreUserInfoInboundInfo struct {
Head common.TomeeHeader `cmd:"2052" struc:"skip"`
UserId uint32 `fieldDescription:"米米号" uint:"true" codec:"true"`
}
// AimatInboundInfo 定义请求或响应数据结构。
type AimatInboundInfo struct {
Head common.TomeeHeader `cmd:"2104" struc:"skip"`
ItemId uint32 `description:"物品id 射击激光 物品id为0" codec:"auto" uint:"true"`
ShootType uint32 `description:"射击类型 未知 给0" codec:"auto" uint:"true"`
Point model.Pos `description:"射击的坐标 x y" codec:"auto"`
}
// ChatInboundInfo 定义请求或响应数据结构。
type ChatInboundInfo struct {
Head common.TomeeHeader `cmd:"2102" struc:"skip"`
Reserve uint32 `json:"reserve" fieldDescription:"填充 默认值为0" uint:"true"`
MessageLen uint32 `struc:"sizeof=Message"`
Message string `json:"message" fieldDescription:"消息内容, 结束符为utf-8的数字0"`
}
// ChangeColorInboundInfo 定义请求或响应数据结构。
type ChangeColorInboundInfo struct {
Head common.TomeeHeader `cmd:"2063" struc:"skip"`
Color uint32 `codec:"color"`
}
// ChangeDoodleInboundInfo 定义请求或响应数据结构。
type ChangeDoodleInboundInfo struct {
Head common.TomeeHeader `cmd:"2062" struc:"skip"`
Id uint32 `codec:"id"`
Color uint32 `codec:"color"`
}
// ChangeNONOColorInboundInfo 定义请求或响应数据结构。
type ChangeNONOColorInboundInfo struct {
Head common.TomeeHeader `cmd:"9012" struc:"skip"`
Color uint32 `codec:"color"`
}
// C2SDanceAction 定义请求或响应数据结构。
type C2SDanceAction struct {
Head common.TomeeHeader `cmd:"2103" struc:"skip"`
Reserve uint32 `struc:"uint32,big"`
Type uint32 `struc:"uint32,big"`
}
// C2SPEOPLE_TRANSFROM 定义请求或响应数据结构。
type C2SPEOPLE_TRANSFROM struct {
Head common.TomeeHeader `cmd:"2111" struc:"skip"`
SuitID uint32 `struc:"uint32,big"`
}
// ChangePlayerNameInboundInfo 定义请求或响应数据结构。
type ChangePlayerNameInboundInfo struct {
Head common.TomeeHeader `cmd:"2061" struc:"skip"`
Nickname string `struc:"[16]byte"`
}
// ChangeTitleInboundInfo 定义请求或响应数据结构。
type ChangeTitleInboundInfo struct {
Head common.TomeeHeader `cmd:"3404" struc:"skip"`
TileID uint32
}
// C2S_GET_GIFT_COMPLETE 定义请求或响应数据结构。
type C2S_GET_GIFT_COMPLETE struct {
Head common.TomeeHeader `cmd:"2801" struc:"skip"`
PassText string `struc:"[16]byte"`
}

View File

@@ -1,86 +1,44 @@
package controller
import (
"blazing/common/data/xmlres"
"blazing/common/socket/errorcode"
"blazing/modules/config/service"
"blazing/logic/service/item"
"blazing/logic/service/player"
"github.com/gogf/gf/v2/util/gconv"
)
// 防止封包通过领取来获取道具
// BuyItem 购买单个道具
// data: 包含购买道具信息的输入数据
// player: 当前玩家对象
// 返回: 购买结果和错误码
func (h Controller) BuyItem(data *item.BuyInboundInfo, player *player.Player) (result *item.BuyOutboundInfo, err errorcode.ErrorCode) {
itemInfo, exists := xmlres.ItemsMAP[int(data.ItemId)]
if !exists {
return &item.BuyOutboundInfo{Coins: player.Info.Coins}, 0
func (h Controller) BuyItem(data *BuyInboundInfo, player *player.Player) (result *item.BuyOutboundInfo, err errorcode.ErrorCode) {
result = &item.BuyOutboundInfo{Coins: player.Info.Coins}
bought, err := buySeerdouBackpackItem(player, data.ItemId, data.Count)
if err != 0 {
return result, err
}
if !bought {
result.Coins = player.Info.Coins
return result, 0
}
// 免费道具直接添加
if itemInfo.Price == 0 {
if player.ItemAdd(data.ItemId, data.Count) {
return &item.BuyOutboundInfo{
ItemId: data.ItemId,
Level: 1,
Count: data.Count,
Coins: player.Info.Coins,
}, 0
}
return &item.BuyOutboundInfo{Coins: player.Info.Coins}, 0
}
// 需要付费的道具
totalCost := data.Count * uint32(itemInfo.Price)
if !player.GetCoins(totalCost) {
return &item.BuyOutboundInfo{Coins: player.Info.Coins}, errorcode.ErrorCodes.ErrSunDouInsufficient10016
}
if player.ItemAdd(data.ItemId, data.Count) {
player.Info.Coins -= totalCost
return &item.BuyOutboundInfo{
ItemId: data.ItemId,
Level: 1,
Count: data.Count,
Coins: player.Info.Coins,
}, 0
}
// 购买失败,返还赛尔豆
player.Info.Coins += totalCost
return &item.BuyOutboundInfo{Coins: player.Info.Coins}, 0
result.ItemId = data.ItemId
result.Level = 1
result.Count = data.Count
result.Coins = player.Info.Coins
return result, 0
}
// BuyMultipleItems 批量购买道具
// data: 包含批量购买道具信息的输入数据
// player: 当前玩家对象
// 返回: 批量购买结果和错误码
func (h Controller) BuyMultipleItems(data *item.BuyMultiInboundInfo, player *player.Player) (result *item.BuyMultiOutboundInfo, err errorcode.ErrorCode) {
func (h Controller) BuyMultipleItems(data *BuyMultiInboundInfo, player *player.Player) (result *item.BuyMultiOutboundInfo, err errorcode.ErrorCode) {
for _, itemID := range data.ItemIds {
itemInfo, exists := xmlres.ItemsMAP[int(itemID)]
if !exists {
continue
}
// 免费道具直接添加
if itemInfo.Price == 0 {
player.ItemAdd(itemID, 1)
continue
}
// 需要付费的道具
if !player.GetCoins(uint32(itemInfo.Price)) {
bought, buyErr := buySeerdouBackpackItem(player, int64(itemID), 1)
if buyErr == errorcode.ErrorCodes.ErrSunDouInsufficient10016 {
break
}
if player.ItemAdd(itemID, 1) {
player.Info.Coins -= uint32(itemInfo.Price)
if buyErr != 0 || !bought {
continue
}
}
@@ -90,24 +48,28 @@ func (h Controller) BuyMultipleItems(data *item.BuyMultiInboundInfo, player *pla
}
// BuyGoldItem 使用金豆购买商品
// data: 包含金豆购买商品信息的输入数据
// player: 当前玩家对象
// 返回: 金豆购买结果和错误码
func (h Controller) BuyGoldItem(data *item.C2S_GOLD_BUY_PRODUCT, player *player.Player) (result *item.S2C_GoldBuyProductInfo, err errorcode.ErrorCode) {
//product, exists := xmlres.GoldProductMap[int(data.ProductID)]
func (h Controller) BuyGoldItem(data *C2S_GOLD_BUY_PRODUCT, player *player.Player) (result *item.S2C_GoldBuyProductInfo, err errorcode.ErrorCode) {
pro := service.NewShopService().Get(data.ProductID)
if pro == nil {
return nil, errorcode.ErrorCodes.ErrSystemError
return nil, errorcode.ErrorCodes.ErrTooManyProducts
}
config := service.NewTalkConfigService().GetCache(int(data.ProductID))
if config != nil {
_, ok := player.Service.Talk.Cheak(0, int(data.ProductID))
if !ok {
return nil, errorcode.ErrorCodes.ErrExceedStock
}
}
var usegold uint64
var addSuccess bool
switch data.Type {
case 0:
if pro.SeerdouPrice == 0 {
return nil, errorcode.ErrorCodes.ErrSystemError
}
if !player.GetCoins(data.Count * uint32(pro.SeerdouPrice)) {
if !player.GetCoins(data.Count * int64(pro.SeerdouPrice)) {
return nil, errorcode.ErrorCodes.ErrSystemError
}
usegold = uint64(data.Count) * uint64(pro.SeerdouPrice)
@@ -117,30 +79,34 @@ func (h Controller) BuyGoldItem(data *item.C2S_GOLD_BUY_PRODUCT, player *player.
return nil, errorcode.ErrorCodes.ErrSystemError
}
if !player.UseGold(uint32(data.Count) * uint32(pro.JindouPrice) * 100) {
if !player.UseGold(int64(data.Count) * int64(pro.JindouPrice) * 100) {
return nil, errorcode.ErrorCodes.ErrSystemError
}
usegold = uint64(data.Count) * uint64(pro.JindouPrice*100)
}
addSuccess = player.ItemAdd(uint32(gconv.Uint32(pro.ProductID)), uint32(data.Count))
if addSuccess {
switch data.Type {
case 0:
player.Info.Coins -= uint32(usegold)
case 1:
player.User.UpdateGold(player.Info.UserID, -int64(usegold))
switch data.Type {
case 0:
if player.ItemAdd(pro.ProductID, data.Count) {
player.Info.Coins -= int64(usegold)
}
player.SendPackCmd(1105, item.GoldOnlineRemainOutboundInfo{
Coin: player.Info.Coins,
GoldNumber: player.User.GetGold(uint(player.Info.UserID)),
})
case 1:
if config != nil {
r := player.Service.Talk.Update(int(pro.ProductID), int(data.Count))
if !r {
return nil, errorcode.ErrorCodes.ErrGoldBeanSingleLimit
}
}
if player.ItemAdd(pro.ProductID, data.Count) {
player.User.UpdateGold(player.Info.UserID, -int64(usegold))
}
}
player.SendPackCmd(1105, item.GoldOnlineRemainOutboundInfo{
Coin: player.Info.Coins,
GoldNumber: uint32(player.User.GetGold(uint(player.Info.UserID))),
})
return nil, -1
}

Some files were not shown because too many files have changed in this diff Show More