Sfoglia il codice sorgente

添加忽略文件夹

jeson_fxd 2 giorni fa
parent
commit
be674dffe8

+ 0 - 625
docs/GM系统API文档.md

@@ -1,625 +0,0 @@
-# GM系统API文档
-
-> 本文档梳理了游戏服务器中所有GM相关的API接口及用途,涵盖管理后台HTTP接口和游戏内GM命令两大部分。
-
----
-
-## 目录
-
-- [一、GM系统架构概述](#一gm系统架构概述)
-- [二、管理后台 HTTP API](#二管理后台-http-api)
-  - [2.1 邮件管理](#21-邮件管理)
-  - [2.2 玩家管理](#22-玩家管理)
-  - [2.3 服务器管理](#23-服务器管理)
-  - [2.4 数据查询与修改](#24-数据查询与修改)
-  - [2.5 运营工具](#25-运营工具)
-  - [2.6 版本与配置管理](#26-版本与配置管理)
-- [三、游戏内GM命令](#三游戏内gm命令)
-  - [3.1 道具/背包命令](#31-道具背包命令)
-  - [3.2 英雄命令](#32-英雄命令)
-  - [3.3 召唤/抽卡命令](#33-召唤抽卡命令)
-  - [3.4 竞技场命令](#34-竞技场命令)
-  - [3.5 活动命令](#35-活动命令)
-  - [3.6 邮件/好友/公会命令](#36-邮件好友公会命令)
-  - [3.7 皮肤/图鉴/标记命令](#37-皮肤图鉴标记命令)
-  - [3.8 账号服务器命令](#38-账号服务器命令)
-  - [3.9 服务端调试命令](#39-服务端调试命令)
-- [四、GM权限管理](#四gm权限管理)
-- [五、GM命令路由流程](#五gm命令路由流程)
-
----
-
-## 一、GM系统架构概述
-
-GM系统分为两层:
-
-| 层级 | 入口 | 说明 |
-|------|------|------|
-| **管理后台 (Admin Web)** | `OpenCards.Service.Admin` HTTP服务 | 面向运营/管理人员的Web后台,提供HTTP API进行玩家管理、服务器管理等操作 |
-| **游戏内控制台 (Client Console)** | 客户端发送 `ClientHandleGMRequest` 协议 | 面向测试/开发人员,在游戏内输入GM命令,由Logic服处理 |
-
-### 核心代码文件
-
-| 文件 | 作用 |
-|------|------|
-| `OpenCards.Server.Core/ConstantDefines/CmdDefines.cs` | GM命令字符串常量定义 |
-| `OpenCards.Server.Common/ServerUtils.cs` | 通用GM命令处理(数据查询、反射调用等) |
-| `OpenCards.Server.Logic/Module/GMCommandModule.cs` | Logic服GM命令注册与路由 |
-| `OpenCards.Server.Account/Modules/AccountServer.GMCommand.cs` | 账号服GM命令处理 |
-| `OpenCards.Service.Admin/Core/ModuleFactory.cs` | Admin后台命令路由工厂 |
-| `OpenCards.Service.Admin/Core/Modules/GmModule.cs` | Admin后台执行GM命令模块 |
-| `OpenCards.Service.Admin/Core/Modules/ServerCmdModule.cs` | Admin后台服务命令模块 |
-| `OpenCards.Core/Protocol/Client/0x35300.Logic.Role.cs` | 客户端GM协议定义 |
-
----
-
-## 二、管理后台 HTTP API
-
-管理后台通过 `ModuleFactory` 根据 `key` 参数路由到不同的处理模块。所有接口均通过 HTTP POST 请求,参数为 JSON 格式。
-
-### 2.1 邮件管理
-
-#### `server_mail` — 发送邮件
-
-- **处理模块**: `SendMailModule`
-- **参数**:
-  - `usertype`: 用户类型(1=在线用户, 2=所有用户, 3=指定用户UUID, 4=指定用户编号)
-  - `mail`: 邮件数据对象(含MailID, Title, Content, Items等)
-  - `server_list`: 目标服务器ID列表(`|`分隔)
-  - `role_list`: 目标角色UUID列表(`|`分隔,指定用户时使用)
-- **用途**: 向指定范围的玩家发送系统邮件,可携带附件道具
-
----
-
-### 2.2 玩家管理
-
-#### `query_server_role_info` — 查询玩家数据
-
-- **处理模块**: `QueryRoleInfoModule`
-- **用途**: 查询单个玩家的详细数据
-
-#### `query_server_role_info_List` — 批量获取玩家数据
-
-- **处理模块**: `QueryRoleInfoListModule`
-- **用途**: 批量查询多个玩家的数据
-
-#### `modify_server_role_info` — 修改玩家数据
-
-- **处理模块**: `ModifyRoleInfoModule`
-- **用途**: 直接修改玩家角色数据(如等级、货币等)
-
-#### `server_mute_role` — 禁言玩家
-
-- **处理模块**: `MuteRoleModule`
-- **用途**: 对指定玩家进行禁言处理
-
-#### `server_block_role` — 封禁玩家
-
-- **处理模块**: `BankRoleModule`
-- **用途**: 对指定玩家进行封号处理
-
-#### `modity_server_login_white_list` — 登录白名单管理
-
-- **处理模块**: `ModifyLoginWhiteListModule`
-- **操作类型**:
-  - `AddMulti`: 批量添加白名单
-  - `DeleteMulti`: 批量删除白名单
-  - `EmptyAll`: 清空白名单
-- **用途**: 管理服务器登录白名单,控制哪些账号可以登录
-
----
-
-### 2.3 服务器管理
-
-#### `server_list_refresh` — 刷新服务器列表
-
-- **处理模块**: `RefreshServerListModule`
-- **用途**: 通知Admin服务刷新服务器列表数据
-
-#### `query_server_state_action` — 查询在线玩家数
-
-- **处理模块**: `QueryServerUserCount`
-- **用途**: 查询各服务器的在线玩家数量
-
-#### `notify_open_server` — 通知开服
-
-- **处理模块**: `NotifyOpenServerGMModule`
-- **参数**:
-  - `is_notify_game_server`: 是否通知游戏服开服
-  - `is_notify_public_server`: 是否通知公共服开服
-  - `operate_server_ids`: 目标服务器ID列表(`|`分隔)
-- **用途**: 通知指定服务器执行开服逻辑(发送 `ServerOpenNotify`)
-
-#### `notify_stop_server` — 通知停服
-
-- **处理模块**: `NotifyStopServerGMModule`
-- **参数**:
-  - `is_kick_game_player`: 是否踢出在线玩家
-  - `is_notify_game_server`: 是否通知游戏服停服
-  - `is_notify_public_server`: 是否通知公共服停服
-  - `operate_server_ids`: 目标服务器ID列表(`|`分隔)
-- **用途**: 通知指定服务器执行停服逻辑,可选踢出在线玩家(发送 `ServerShutdownNotify` / `StopServerKickRequest`)
-
-#### `stop_server_change_data` — 停服数据刷新
-
-- **处理模块**: `RefreshStopServerDataModule`
-- **用途**: 刷新停服相关数据
-
-#### `check_login_flow` — 检查登录流程
-
-- **处理模块**: `CheckLoginFlowModule`
-- **用途**: 获取登录流程未走通的服务器列表数据
-
----
-
-### 2.4 数据查询与修改
-
-#### `server_gm` — 执行GM命令
-
-- **处理模块**: `GmModule`
-- **参数**:
-  - `gmType`: 用户类型(1=在线用户, 2=所有用户, 3=指定用户UUID)
-  - `server_list`: 目标服务器ID列表(`|`分隔)
-  - `role_list`: 目标角色UUID列表(`|`分隔)
-  - `content`: GM命令内容(与游戏内GM命令格式一致)
-- **用途**: 通过管理后台向指定玩家/服务器发送GM命令(底层发送 `HandleGMNotify` 协议)
-
-#### `server_cmd` — 服务命令
-
-- **处理模块**: `ServerCmdModule`
-- **参数**:
-  - `serviceAddr`: 目标服务地址
-  - `operationType`: 操作类型(`notify` 发送通知 / `request` 发送请求并等待响应)
-  - `notify` / `request`: 具体的协议数据(格式: `FullTypeName:{json}`)
-- **用途**: 向指定服务进程直接发送协议消息,用于底层调试
-
-#### `set_server_time` — 设置服务器时间
-
-- **处理模块**: `SetServerTimeModule`
-- **用途**: 修改服务器时间偏移(用于活动测试等场景)
-
----
-
-### 2.5 运营工具
-
-#### `cdk_opt` — CDK操作
-
-- **处理模块**: `CdkModule`
-- **用途**: CDK(礼包兑换码)的生成、查询、管理等操作
-
-#### `send_pay_gift` — 发送充值礼包
-
-- **处理模块**: `SendPayGiftModule`
-- **用途**: 向玩家发送充值相关礼包
-
-#### `change_pay_order_state` — 修改支付订单状态
-
-- **处理模块**: `ChangePayOrderStateModule`
-- **用途**: 修改玩家的支付订单状态
-
----
-
-### 2.6 版本与配置管理
-
-#### `operate_client_accept_version` — 客户端版本管理
-
-- **处理模块**: `ModifyOrQueryClientAcceptVersionModule`
-- **用途**: 修改或查询客户端可接受的版本号
-
-#### `operate_update_server_config` — 更新服务器配置
-
-- **处理模块**: `OperateUpdateServerConfigModule`
-- **用途**: 在线更新服务器配置
-
-#### `operate_client_version_zip_config` — 客户端版本包配置
-
-- **处理模块**: `OperateClientVersionAndZipConfigModule`
-- **用途**: 管理客户端版本更新包配置
-
-#### `server_protocol_switch` — 服务器协议开关
-
-- **处理模块**: `ServerProtocolModule`
-- **用途**: 控制服务器各类协议功能的开关状态
-
----
-
-## 三、游戏内GM命令
-
-游戏内GM命令通过客户端发送 `ClientHandleGMRequest` 协议,命令格式为空格分隔的字符串,由 `LogicService` 分发给各模块处理。
-
-### 3.1 道具/背包命令
-
-> **处理模块**: `PackageModule`
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `additem` | `<itemId> <count> [type]` | 添加指定ID的道具,可指定道具类型(默认Tool类型) |
-| `addallitem` | `<count> [type]` | 添加配置表中该类型的所有道具各count个 |
-| `addgold` | 无 | 添加1亿金币(CoinItemId) |
-| `adddiam` | 无 | 添加1亿钻石(DiamondItemId) |
-| `addexp` | 无 | 添加1亿英雄经验(HeroExpItemId) |
-| `listitems` | 无 | 列出背包中所有道具(uuid, itemId, count, type) |
-| `useitem` | `<itemId> <count> [selectedItemId]` | 使用指定ID的道具 |
-| `sellitem` | `<itemId> <count>` | 出售指定ID的道具 |
-| `removeitem` | `<itemId> <count>` | 删除指定ID的道具 |
-| `drop` | `<dropId>` | 按掉落表ID执行掉落 |
-| `clearpackage` | 无 | 清空整个背包(包括道具、金币、钻石、VIP经验) |
-
----
-
-### 3.2 英雄命令
-
-> **处理模块**: `HeroModule`
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `addhero` | `<heroId> <count> <level> <rank>` | 添加指定英雄,可指定数量、等级、品阶 |
-| `listhero` | `[level]` | 列出所有英雄,可选设置统一等级 |
-| `herouuid` | 无 | 列出所有英雄的UUID |
-| `addAllHero` | `<count>` | 添加配置表中所有启用的英雄(IsUsing=1)各count个 |
-| `addAllHero2` | `<count> <level> <quality>` | 添加所有可用英雄(IsUsing=1 或 IsAvailable=1),可指定等级和品阶 |
-| `setHeroPackCount` | `<count>` | 设置英雄背包容量(受 `BuyHeroBagMaxCount` 限制) |
-
----
-
-### 3.3 召唤/抽卡命令
-
-> **处理模块**: `SummonModule`
-
-召唤类型(type)对应 `SyncClientSummonType` 枚举:
-- 钻石抽卡 (Diamond)
-- 友情抽卡 (Friend)
-- 占星抽卡 (Astrology)
-- 限时抽卡 (TimeLimited)
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `summonsingle` | `<type>` | 单次抽卡 |
-| `summonten` | `<type>` | 十连抽卡 |
-| `addsummon` | `<type> <count>` | 批量抽卡(count上限10000) |
-| `adddrawpoints` | `<count>` | 添加抽卡积分(DrawPoints) |
-| `testWishSummon` | `<count>` | 测试心愿召唤(上限10000次),在 `AtlantisActivityModule` 中处理 |
-
----
-
-### 3.4 竞技场命令
-
-> **处理模块**: `ArenaModule`
-
-#### 斗技场 (Arena Valor) 相关
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `enterArenaValor` | 无 | 进入斗技场(自动执行进竞技场+获取数据) |
-| `enterArenaValor1` | 无 | 一键进入斗技场(自动解锁关卡5-1、添加所有英雄、设置随机分数2000-2900) |
-| `setArenaScore` | `<score>` | 设置斗技场积分 |
-| `fillarenavalorgroup` | 无 | 填充斗技场分组(转发到ArenaManagerService处理) |
-| `initarenavalorrobotrank` | 无 | 初始化斗技场机器人排名(转发到ArenaValorService处理) |
-
-#### 血战斗技场 (Arena Highend) 相关
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `enterArenaHighend` | 无 | 进入血战斗技场(自动执行进竞技场+获取数据) |
-| `setArenaHighendTier` | `<tier>` | 设置血战斗技场段位 |
-| `addRoleInArenaHighendScoreRank` | 无 | 将当前角色加入血战斗技场积分排行榜 |
-
-#### 巅峰竞技场 (Arena Pinnacle) 相关
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `nextArenaPinnacleStage` | 无 | 推进巅峰竞技场到下一阶段(修改时间偏移) |
-| `backArenaPinnacleStage` | 无 | 回退巅峰竞技场到上一阶段(修改时间偏移) |
-| `updateArenaPinnacleRegionConfig` | `<isAutoDistributeRegion> <...>` | 更新巅峰竞技场赛区配置 |
-| `preparePinnacleFakeData` | 无 | 准备巅峰竞技场假数据(代码已注释,暂不可用) |
-
-#### 竞技场通用调试命令
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `initarenarobotdatamapping` | 无 | 初始化竞技场机器人数据映射(转发到ArenaManagerService处理) |
-| `matchArenaRealPlayer` | `<state>` | 设置竞技场匹配真人开关(0=关闭/非0=开启,遍历所有Valor+Highend服务) |
-| `setNewArenaSeason` | `<flag> <count>` | 设置新竞技场赛季(flag=3为巅峰竞技场,代码已注释) |
-| `setTimeOffset` | `<cmd1> <cmd2>` | 设置服务器时间偏移(需 `IsGMTime` 开关开启) |
-| `printiostatistics` | 无 | 打印IO统计信息到桌面文件(仅Windows) |
-
-#### 账号相关
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `cleanUpAccount` | 无 | 清理账号数据(转发clearaccount到AccountService) |
-
----
-
-### 3.5 活动命令
-
-> **处理模块**: `ActivityModule`
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `targettask` | `<taskId> <count>` | 推进指定目标任务的进度(达到目标后自动设为可领取状态) |
-| `activitytask` | `<taskId> <count>` | 推进指定活动任务的进度(达到目标后自动设为可领取状态) |
-| `setthirtyperiod` | `<period>` | 设置30日签到活动周期(只能向后推进) |
-
----
-
-### 3.6 邮件/好友/公会命令
-
-#### 邮件
-
-> **处理模块**: `MailModule`
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `sendmail` | `<mailId> <param2> <param3>` | 根据邮件模板ID发送邮件给当前角色 |
-
-#### 好友
-
-> **处理模块**: `FriendModule`
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `addmercenary` | `<heroId> <heroRank>` | 添加好友佣兵英雄 |
-| `resetmercenaryuse` | 无 | 重置佣兵使用次数记录 |
-
-#### 公会
-
-> **处理模块**: `GuildWarriorTreasureModule`
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `addguildwarriortreasure` | `<configId> <args>` | 添加公会勇者宝藏(args为逗号分隔的参数) |
-| `addgwt` | `<configId> <args>` | `addguildwarriortreasure` 的缩写别名(通过GMCommandModule路由) |
-
-#### 新公会Boss
-
-> **处理模块**: `NewGuildBossModule`
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `SetNewGuildBossStage` | `<stageNum>` | 设置新公会Boss的关卡阶段 |
-
----
-
-### 3.7 皮肤/图鉴/标记命令
-
-#### 皮肤
-
-> **处理模块**: `SkinModule`
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `unlockAllSkin` | 无 | 解锁所有皮肤(遍历Item表中ItemType=Skin的所有道具添加) |
-
-#### 角色标记
-
-> **处理模块**: `TLRoleFlagModule`(通过 `GMCommandModule` 路由)
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `addflag` | `<key> <value> <type>` | 添加角色标记(累加模式),type=1表示1日标记,其他表示永久标记 |
-| `setflag` | `<key> <value> <type>` | 设置角色标记(覆盖模式),type=1表示1日标记,其他表示永久标记 |
-
-#### 新手引导
-
-> **处理模块**: `NewerGuideModule`(通过 `GMCommandModule` 路由)
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `guide` | `<id> <step>` | 设置新手引导进度到指定步骤 |
-
----
-
-### 3.8 账号服务器命令
-
-> **处理模块**: `AccountServer`(通过 `HandleGMRequest` RPC调用)
-
-这些命令在AccountServer层直接拦截处理,不会转发到Logic服:
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `clearaccount` | `<accountID>` | 清除指定账号的数据(清理AccountDataMapping,踢出在线玩家) |
-| `resettimers` | 无 | 重置账号数据保存定时器 |
-| `relinkrole` | `<roleId> <account> <serverId>` | 重新绑定角色到指定账号(创建新AccountData并关联角色) |
-
----
-
-### 3.9 服务端调试命令
-
-> **处理模块**: `ServerUtils.HandleGMRequest()` — 这些命令可在任何服务进程中执行
-
-#### 数据查询与修改
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `queryData` | `<prefix> <typeName> [fieldName]` | 查询ORM映射数据。prefix为数据前缀,typeName为ORM类型全名,可选fieldName查询指定字段 |
-| `saveData` | `<prefix> <typeName> <jsonData>` | 保存ORM映射数据。将JSON数据合并到现有数据中 |
-| `saveFieldData` | `<prefix> <typeName> <fieldName> <jsonData>` | 保存ORM数据的指定字段。当typeName为RoleData/RoleExtData时,路由到在线Logic服处理 |
-| `queryfield` | `<fieldName>` | 通过反射查询当前Service对象的字段值 |
-| `savefield` | `<fieldName> <jsonValue>` | 通过反射设置当前Service对象的字段值 |
-| `queryproperty` | `<propertyName>` | 通过反射查询当前Service对象的属性值 |
-| `saveproperty` | `<propertyName> <jsonValue>` | 通过反射设置当前Service对象的属性值 |
-| `InvokeMethod` | `<methodName> [params...]` | 通过反射调用当前Service对象的指定方法,参数以JSON格式传递 |
-
-#### Redis操作
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `redisexecute` | `<cmd> [args...]` | 直接执行Redis命令(不返回结果) |
-| `redisexecute1` | `<cmd> [args...]` | 执行Redis命令并返回字符串结果 |
-| `redisquery` | `<cmd> [args...]` | 查询Redis,返回数组格式结果 |
-| `redisquery2` | `<cmd> [args...]` | 查询Redis,返回字典格式结果 |
-
-#### 系统工具
-
-| 命令 | 参数 | 用途 |
-|------|------|------|
-| `ping` | 无 | 连通性测试,返回"success" |
-| `errorstatistic` | 无 | 打印错误统计信息到文件 |
-| `setconstantvalue` | `<key> <field> <value>` | 动态修改常量表(Table_ConstantManager)的值 |
-| `exesh` | `<command>` | 在服务器上执行Shell命令(仅Linux环境) |
-
----
-
-## 四、GM权限管理
-
-### 数据库结构
-
-GM权限数据存储在 `afk_gm` 数据库中,包含以下表:
-
-#### `permissions` — 权限组表
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| Id | INTEGER | 主键自增 |
-| Name | VARCHAR(255) | 权限组名称(唯一) |
-| Permissions | TEXT | 权限字符串(`|`和`-`分隔的层级权限编码) |
-| Status | BOOLEAN | 状态(0=未启用, 1=已启用) |
-
-默认数据:
-- **超级管理员**: Permissions=`1|1-1|1-2|2|2-1|2-2|2-3|3|3-1|4|4-1|4-2|4-3`,Status=启用
-
-#### `users` — 用户表
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| Id | INTEGER | 主键自增 |
-| Account | VARCHAR(255) | 账号(唯一) |
-| Password | VARCHAR(255) | 密码(MD5加密) |
-| Name | VARCHAR(255) | 用户名称 |
-| Pid | INTEGER | 关联权限组ID(外键→permissions.Id) |
-| Status | BOOLEAN | 状态(0=未启用, 1=已启用) |
-
-默认数据:
-- **超管**: Account=`root`, Password=`4DCBE8484EA8AFBB1BB67450C5C08503`(MD5加密), Pid=1
-
-#### `server_group` — 服务器组表
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| Id | INTEGER | 主键自增 |
-| GroupName | VARCHAR(255) | 组名 |
-| Address | VARCHAR(255) | 服务器地址 |
-| AccountPort | INTEGER | 账号服端口 |
-| AdminPort | INTEGER | 管理后台端口 |
-
-#### `logs` — 操作日志表
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| Id | INTEGER | 主键自增 |
-| Type | VARCHAR(255) | 操作类型 |
-| Args | TEXT | 操作参数 |
-| Success | BOOLEAN | 是否成功 |
-| Reason | VARCHAR(255) | 失败原因 |
-| Operator | VARCHAR(255) | 操作者 |
-| CreateAt | DATETIME | 操作时间 |
-
-#### `mails` — 邮件记录表
-
-| 字段 | 类型 | 说明 |
-|------|------|------|
-| Id | INTEGER | 主键自增(起始值10000) |
-| Type | INTEGER | 邮件类型 |
-| ServerList | VARCHAR(255) | 目标服务器列表 |
-| RoleList | VARCHAR(255) | 目标角色列表 |
-| Title | VARCHAR(255) | 邮件标题 |
-| Content | TEXT | 邮件内容 |
-| Items | TEXT | 邮件附件 |
-| Success | BOOLEAN | 是否发送成功 |
-| CreateAt | INTEGER | 创建时间 |
-
----
-
-## 五、GM命令路由流程
-
-### 游戏内GM命令执行流程
-
-```
-客户端输入GM命令
-    │
-    ▼
-ClientHandleGMRequest (协议: LOGIC_ROLE_START+1)
-    │
-    ▼
-RoleModule.HandleMsgClientHandleGMRequest()
-    │
-    ├─► 检查是否是AccountServer命令 (clearaccount/resettimers/relinkrole)
-    │     └─► 是 → 转发到 AccountServer.rpc_Handle()
-    │
-    └─► 否 → 触发 EventDefines.EventHandleCmd 事件
-          │
-          ├─► GMCommandModule.OnEventHandleCmd()
-          │     ├─ addflag → TLRoleFlagModule.OnGmAddFlag()
-          │     ├─ setflag → TLRoleFlagModule.OnGmSetFlag()
-          │     ├─ addgwt → GuildWarriorTreasureModule.OnGmAddGuildWarriorTreasure()
-          │     └─ guide → NewerGuideModule.OnGmGuide()
-          │
-          ├─► PackageModule.OnEventHandleCmd()
-          │     └─ additem/addgold/adddiam/... 等道具命令
-          │
-          ├─► HeroModule.OnEventHandleCmd()
-          │     └─ addhero/listhero/addAllHero/... 等英雄命令
-          │
-          ├─► SummonModule.OnEventHandleCmd()
-          │     └─ summonsingle/summonten/addsummon/... 等召唤命令
-          │
-          ├─► ArenaModule.OnEventHandleCmd()
-          │     └─ setArenaScore/enterArenaValor/... 等竞技场命令
-          │
-          ├─► ActivityModule.OnEventHandleCmd()
-          │     └─ targettask/activitytask/setthirtyperiod 等活动命令
-          │
-          ├─► MailModule.OnEventHandleCmd()
-          │     └─ sendmail
-          │
-          ├─► FriendModule.OnEventHandleCmd()
-          │     └─ addmercenary/resetmercenaryuse
-          │
-          ├─► SkinModule.OnEventHandleCmd()
-          │     └─ unlockAllSkin
-          │
-          ├─► NewGuildBossModule.OnEventHandleCmd()
-          │     └─ SetNewGuildBossStage
-          │
-          ├─► GuildWarriorTreasureModule.OnEventHandleCmd()
-          │     └─ addguildwarriortreasure
-          │
-          └─► 其他模块的 OnEventHandleCmd()...
-                └─ 未处理 → ServerUtils.HandleGMRequest() 兜底处理
-                      └─ queryData/saveData/redisquery/... 等通用命令
-```
-
-### 管理后台GM命令执行流程
-
-```
-Admin HTTP请求
-    │
-    ▼
-ModuleFactory.GetModuleByStr(key)
-    │
-    ├─► server_gm → GmModule.OnHandle()
-    │     └─► 根据 gmType 发送 HandleGMNotify 到指定服务器
-    │           ├─ OnlineUser → ConnectorService (ConnectorBroadcastNotify)
-    │           ├─ AllUser → CenterService (HandleGMNotify)
-    │           └─ SpecificUserUuids → CenterService (HandleGMNotify)
-    │
-    ├─► server_cmd → ServerCmdModule.OnHandle()
-    │     └─► 根据 operationType 发送协议到指定服务
-    │
-    ├─► server_mail → SendMailModule.OnHandle()
-    │     └─► 根据 usertype 发送 SendMailNotify
-    │
-    ├─► notify_open_server → NotifyOpenServerGMModule.OnHandle()
-    │     └─► 发送 ServerOpenNotify
-    │
-    ├─► notify_stop_server → NotifyStopServerGMModule.OnHandle()
-    │     └─► 发送 StopServerKickRequest / ServerShutdownNotify
-    │
-    └─► 其他模块...
-```
-
----
-
-> 文档生成时间:2026年6月11日
-> 基于代码分支:dev

+ 0 - 1887
docs/dotnet-install.sh

@@ -1,1887 +0,0 @@
-#!/usr/bin/env bash
-# Copyright (c) .NET Foundation and contributors. All rights reserved.
-# Licensed under the MIT license. See LICENSE file in the project root for full license information.
-#
-
-# Stop script on NZEC
-set -e
-# Stop script if unbound variable found (use ${var:-} if intentional)
-set -u
-# By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success
-# This is causing it to fail
-set -o pipefail
-
-# Use in the the functions: eval $invocation
-invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"'
-
-# standard output may be used as a return value in the functions
-# we need a way to write text on the screen in the functions so that
-# it won't interfere with the return value.
-# Exposing stream 3 as a pipe to standard output of the script itself
-exec 3>&1
-
-# Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors.
-# See if stdout is a terminal
-if [ -t 1 ] && command -v tput > /dev/null; then
-    # see if it supports colors
-    ncolors=$(tput colors || echo 0)
-    if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then
-        bold="$(tput bold       || echo)"
-        normal="$(tput sgr0     || echo)"
-        black="$(tput setaf 0   || echo)"
-        red="$(tput setaf 1     || echo)"
-        green="$(tput setaf 2   || echo)"
-        yellow="$(tput setaf 3  || echo)"
-        blue="$(tput setaf 4    || echo)"
-        magenta="$(tput setaf 5 || echo)"
-        cyan="$(tput setaf 6    || echo)"
-        white="$(tput setaf 7   || echo)"
-    fi
-fi
-
-say_warning() {
-    printf "%b\n" "${yellow:-}dotnet_install: Warning: $1${normal:-}" >&3
-}
-
-say_err() {
-    printf "%b\n" "${red:-}dotnet_install: Error: $1${normal:-}" >&2
-}
-
-say() {
-    # using stream 3 (defined in the beginning) to not interfere with stdout of functions
-    # which may be used as return value
-    printf "%b\n" "${cyan:-}dotnet-install:${normal:-} $1" >&3
-}
-
-say_verbose() {
-    if [ "$verbose" = true ]; then
-        say "$1"
-    fi
-}
-
-# This platform list is finite - if the SDK/Runtime has supported Linux distribution-specific assets,
-#   then and only then should the Linux distribution appear in this list.
-# Adding a Linux distribution to this list does not imply distribution-specific support.
-get_legacy_os_name_from_platform() {
-    eval $invocation
-
-    platform="$1"
-    case "$platform" in
-        "centos.7")
-            echo "centos"
-            return 0
-            ;;
-        "debian.8")
-            echo "debian"
-            return 0
-            ;;
-        "debian.9")
-            echo "debian.9"
-            return 0
-            ;;
-        "fedora.23")
-            echo "fedora.23"
-            return 0
-            ;;
-        "fedora.24")
-            echo "fedora.24"
-            return 0
-            ;;
-        "fedora.27")
-            echo "fedora.27"
-            return 0
-            ;;
-        "fedora.28")
-            echo "fedora.28"
-            return 0
-            ;;
-        "opensuse.13.2")
-            echo "opensuse.13.2"
-            return 0
-            ;;
-        "opensuse.42.1")
-            echo "opensuse.42.1"
-            return 0
-            ;;
-        "opensuse.42.3")
-            echo "opensuse.42.3"
-            return 0
-            ;;
-        "rhel.7"*)
-            echo "rhel"
-            return 0
-            ;;
-        "ubuntu.14.04")
-            echo "ubuntu"
-            return 0
-            ;;
-        "ubuntu.16.04")
-            echo "ubuntu.16.04"
-            return 0
-            ;;
-        "ubuntu.16.10")
-            echo "ubuntu.16.10"
-            return 0
-            ;;
-        "ubuntu.18.04")
-            echo "ubuntu.18.04"
-            return 0
-            ;;
-        "alpine.3.4.3")
-            echo "alpine"
-            return 0
-            ;;
-    esac
-    return 1
-}
-
-get_legacy_os_name() {
-    eval $invocation
-
-    local uname=$(uname)
-    if [ "$uname" = "Darwin" ]; then
-        echo "osx"
-        return 0
-    elif [ -n "$runtime_id" ]; then
-        echo $(get_legacy_os_name_from_platform "${runtime_id%-*}" || echo "${runtime_id%-*}")
-        return 0
-    else
-        if [ -e /etc/os-release ]; then
-            . /etc/os-release
-            os=$(get_legacy_os_name_from_platform "$ID${VERSION_ID:+.${VERSION_ID}}" || echo "")
-            if [ -n "$os" ]; then
-                echo "$os"
-                return 0
-            fi
-        fi
-    fi
-
-    say_verbose "Distribution specific OS name and version could not be detected: UName = $uname"
-    return 1
-}
-
-get_linux_platform_name() {
-    eval $invocation
-
-    if [ -n "$runtime_id" ]; then
-        echo "${runtime_id%-*}"
-        return 0
-    else
-        if [ -e /etc/os-release ]; then
-            . /etc/os-release
-            echo "$ID${VERSION_ID:+.${VERSION_ID}}"
-            return 0
-        elif [ -e /etc/redhat-release ]; then
-            local redhatRelease=$(</etc/redhat-release)
-            if [[ $redhatRelease == "CentOS release 6."* || $redhatRelease == "Red Hat Enterprise Linux "*" release 6."* ]]; then
-                echo "rhel.6"
-                return 0
-            fi
-        fi
-    fi
-
-    say_verbose "Linux specific platform name and version could not be detected: UName = $uname"
-    return 1
-}
-
-is_musl_based_distro() {
-    (ldd --version 2>&1 || true) | grep -q musl
-}
-
-get_current_os_name() {
-    eval $invocation
-
-    local uname=$(uname)
-    if [ "$uname" = "Darwin" ]; then
-        echo "osx"
-        return 0
-    elif [ "$uname" = "FreeBSD" ]; then
-        echo "freebsd"
-        return 0
-    elif [ "$uname" = "Linux" ]; then
-        local linux_platform_name=""
-        linux_platform_name="$(get_linux_platform_name)" || true
-
-        if [ "$linux_platform_name" = "rhel.6" ]; then
-            echo $linux_platform_name
-            return 0
-        elif is_musl_based_distro; then
-            echo "linux-musl"
-            return 0
-        elif [ "$linux_platform_name" = "linux-musl" ]; then
-            echo "linux-musl"
-            return 0
-        else
-            echo "linux"
-            return 0
-        fi
-    fi
-
-    say_err "OS name could not be detected: UName = $uname"
-    return 1
-}
-
-machine_has() {
-    eval $invocation
-
-    command -v "$1" > /dev/null 2>&1
-    return $?
-}
-
-check_min_reqs() {
-    local hasMinimum=false
-    if machine_has "curl"; then
-        hasMinimum=true
-    elif machine_has "wget"; then
-        hasMinimum=true
-    fi
-
-    if [ "$hasMinimum" = "false" ]; then
-        say_err "curl (recommended) or wget are required to download dotnet. Install missing prerequisite to proceed."
-        return 1
-    fi
-    return 0
-}
-
-# args:
-# input - $1
-to_lowercase() {
-    #eval $invocation
-
-    echo "$1" | tr '[:upper:]' '[:lower:]'
-    return 0
-}
-
-# args:
-# input - $1
-remove_trailing_slash() {
-    #eval $invocation
-
-    local input="${1:-}"
-    echo "${input%/}"
-    return 0
-}
-
-# args:
-# input - $1
-remove_beginning_slash() {
-    #eval $invocation
-
-    local input="${1:-}"
-    echo "${input#/}"
-    return 0
-}
-
-# args:
-# root_path - $1
-# child_path - $2 - this parameter can be empty
-combine_paths() {
-    eval $invocation
-
-    # TODO: Consider making it work with any number of paths. For now:
-    if [ ! -z "${3:-}" ]; then
-        say_err "combine_paths: Function takes two parameters."
-        return 1
-    fi
-
-    local root_path="$(remove_trailing_slash "$1")"
-    local child_path="$(remove_beginning_slash "${2:-}")"
-    say_verbose "combine_paths: root_path=$root_path"
-    say_verbose "combine_paths: child_path=$child_path"
-    echo "$root_path/$child_path"
-    return 0
-}
-
-get_machine_architecture() {
-    eval $invocation
-
-    if command -v uname > /dev/null; then
-        CPUName=$(uname -m)
-        case $CPUName in
-        armv1*|armv2*|armv3*|armv4*|armv5*|armv6*)
-            echo "armv6-or-below"
-            return 0
-            ;;
-        armv*l)
-            echo "arm"
-            return 0
-            ;;
-        aarch64|arm64)
-            if [ "$(getconf LONG_BIT)" -lt 64 ]; then
-                # This is 32-bit OS running on 64-bit CPU (for example Raspberry Pi OS)
-                echo "arm"
-                return 0
-            fi
-            echo "arm64"
-            return 0
-            ;;
-        s390x)
-            echo "s390x"
-            return 0
-            ;;
-        ppc64le)
-            echo "ppc64le"
-            return 0
-            ;;
-        loongarch64)
-            echo "loongarch64"
-            return 0
-            ;;
-        riscv64)
-            echo "riscv64"
-            return 0
-            ;;
-        powerpc|ppc)
-            echo "ppc"
-            return 0
-            ;;
-        esac
-    fi
-
-    # Always default to 'x64'
-    echo "x64"
-    return 0
-}
-
-# args:
-# architecture - $1
-get_normalized_architecture_from_architecture() {
-    eval $invocation
-
-    local architecture="$(to_lowercase "$1")"
-
-    if [[ $architecture == \<auto\> ]]; then
-        machine_architecture="$(get_machine_architecture)"
-        if [[ "$machine_architecture" == "armv6-or-below" ]]; then
-            say_err "Architecture \`$machine_architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues"
-            return 1
-        fi
-
-        echo $machine_architecture
-        return 0
-    fi
-
-    case "$architecture" in
-        amd64|x64)
-            echo "x64"
-            return 0
-            ;;
-        arm)
-            echo "arm"
-            return 0
-            ;;
-        arm64)
-            echo "arm64"
-            return 0
-            ;;
-        s390x)
-            echo "s390x"
-            return 0
-            ;;
-        ppc64le)
-            echo "ppc64le"
-            return 0
-            ;;
-        loongarch64)
-            echo "loongarch64"
-            return 0
-            ;;
-    esac
-
-    say_err "Architecture \`$architecture\` not supported. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues"
-    return 1
-}
-
-# args:
-# version - $1
-# channel - $2
-# architecture - $3
-get_normalized_architecture_for_specific_sdk_version() {
-    eval $invocation
-
-    local is_version_support_arm64="$(is_arm64_supported "$1")"
-    local is_channel_support_arm64="$(is_arm64_supported "$2")"
-    local architecture="$3";
-    local osname="$(get_current_os_name)"
-
-    if [ "$osname" == "osx" ] && [ "$architecture" == "arm64" ] && { [ "$is_version_support_arm64" = false ] || [ "$is_channel_support_arm64" = false ]; }; then
-        #check if rosetta is installed
-        if [ "$(/usr/bin/pgrep oahd >/dev/null 2>&1;echo $?)" -eq 0 ]; then 
-            say_verbose "Changing user architecture from '$architecture' to 'x64' because .NET SDKs prior to version 6.0 do not support arm64." 
-            echo "x64"
-            return 0;
-        else
-            say_err "Architecture \`$architecture\` is not supported for .NET SDK version \`$version\`. Please install Rosetta to allow emulation of the \`$architecture\` .NET SDK on this platform"
-            return 1
-        fi
-    fi
-
-    echo "$architecture"
-    return 0
-}
-
-# args:
-# version or channel - $1
-is_arm64_supported() {
-    # Extract the major version by splitting on the dot
-    major_version="${1%%.*}"
-
-    # Check if the major version is a valid number and less than 6
-    case "$major_version" in
-        [0-9]*)  
-            if [ "$major_version" -lt 6 ]; then
-                echo false
-                return 0
-            fi
-            ;;
-    esac
-
-    echo true
-    return 0
-}
-
-# args:
-# user_defined_os - $1
-get_normalized_os() {
-    eval $invocation
-
-    local osname="$(to_lowercase "$1")"
-    if [ ! -z "$osname" ]; then
-        case "$osname" in
-            osx | freebsd | rhel.6 | linux-musl | linux)
-                echo "$osname"
-                return 0
-                ;;
-            macos)
-                osname='osx'
-                echo "$osname"
-                return 0
-                ;;
-            *)
-                say_err "'$user_defined_os' is not a supported value for --os option, supported values are: osx, macos, linux, linux-musl, freebsd, rhel.6. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues."
-                return 1
-                ;;
-        esac
-    else
-        osname="$(get_current_os_name)" || return 1
-    fi
-    echo "$osname"
-    return 0
-}
-
-# args:
-# quality - $1
-get_normalized_quality() {
-    eval $invocation
-
-    local quality="$(to_lowercase "$1")"
-    if [ ! -z "$quality" ]; then
-        case "$quality" in
-            daily | preview)
-                echo "$quality"
-                return 0
-                ;;
-            ga)
-                #ga quality is available without specifying quality, so normalizing it to empty
-                return 0
-                ;;
-            *)
-                say_err "'$quality' is not a supported value for --quality option. Supported values are: daily, preview, ga. If you think this is a bug, report it at https://github.com/dotnet/install-scripts/issues."
-                return 1
-                ;;
-        esac
-    fi
-    return 0
-}
-
-# args:
-# channel - $1
-get_normalized_channel() {
-    eval $invocation
-
-    local channel="$(to_lowercase "$1")"
-
-    if [[ $channel == current ]]; then
-        say_warning 'Value "Current" is deprecated for -Channel option. Use "STS" instead.'
-    fi
-
-    if [[ $channel == release/* ]]; then
-        say_warning 'Using branch name with -Channel option is no longer supported with newer releases. Use -Quality option with a channel in X.Y format instead.';
-    fi
-
-    if [ ! -z "$channel" ]; then
-        case "$channel" in
-            lts)
-                echo "LTS"
-                return 0
-                ;;
-            sts)
-                echo "STS"
-                return 0
-                ;;
-            current)
-                echo "STS"
-                return 0
-                ;;
-            *)
-                echo "$channel"
-                return 0
-                ;;
-        esac
-    fi
-
-    return 0
-}
-
-# args:
-# runtime - $1
-get_normalized_product() {
-    eval $invocation
-
-    local product=""
-    local runtime="$(to_lowercase "$1")"
-    if [[ "$runtime" == "dotnet" ]]; then
-        product="dotnet-runtime"
-    elif [[ "$runtime" == "aspnetcore" ]]; then
-        product="aspnetcore-runtime"
-    elif [ -z "$runtime" ]; then
-        product="dotnet-sdk"
-    fi
-    echo "$product"
-    return 0
-}
-
-# The version text returned from the feeds is a 1-line or 2-line string:
-# For the SDK and the dotnet runtime (2 lines):
-# Line 1: # commit_hash
-# Line 2: # 4-part version
-# For the aspnetcore runtime (1 line):
-# Line 1: # 4-part version
-
-# args:
-# version_text - stdin
-get_version_from_latestversion_file_content() {
-    eval $invocation
-
-    cat | tail -n 1 | sed 's/\r$//'
-    return 0
-}
-
-# args:
-# install_root - $1
-# relative_path_to_package - $2
-# specific_version - $3
-is_dotnet_package_installed() {
-    eval $invocation
-
-    local install_root="$1"
-    local relative_path_to_package="$2"
-    local specific_version="${3//[$'\t\r\n']}"
-
-    local dotnet_package_path="$(combine_paths "$(combine_paths "$install_root" "$relative_path_to_package")" "$specific_version")"
-    say_verbose "is_dotnet_package_installed: dotnet_package_path=$dotnet_package_path"
-
-    if [ -d "$dotnet_package_path" ]; then
-        return 0
-    else
-        return 1
-    fi
-}
-
-# args:
-# downloaded file - $1
-# remote_file_size - $2
-validate_remote_local_file_sizes() 
-{
-    eval $invocation
-
-    local downloaded_file="$1"
-    local remote_file_size="$2"
-    local file_size=''
-
-    if [[ "$OSTYPE" == "linux-gnu"* ]]; then
-        file_size="$(stat -c '%s' "$downloaded_file")"
-    elif [[ "$OSTYPE" == "darwin"* ]]; then
-        # hardcode in order to avoid conflicts with GNU stat
-        file_size="$(/usr/bin/stat -f '%z' "$downloaded_file")"
-    fi  
-    
-    if [ -n "$file_size" ]; then
-        say "Downloaded file size is $file_size bytes."
-
-        if [ -n "$remote_file_size" ] && [ -n "$file_size" ]; then
-            if [ "$remote_file_size" -ne "$file_size" ]; then
-                say "The remote and local file sizes are not equal. The remote file size is $remote_file_size bytes and the local size is $file_size bytes. The local package may be corrupted."
-            else
-                say "The remote and local file sizes are equal."
-            fi
-        fi
-        
-    else
-        say "Either downloaded or local package size can not be measured. One of them may be corrupted."      
-    fi 
-}
-
-# args:
-# azure_feed - $1
-# channel - $2
-# normalized_architecture - $3
-get_version_from_latestversion_file() {
-    eval $invocation
-
-    local azure_feed="$1"
-    local channel="$2"
-    local normalized_architecture="$3"
-
-    local version_file_url=null
-    if [[ "$runtime" == "dotnet" ]]; then
-        version_file_url="$azure_feed/Runtime/$channel/latest.version"
-    elif [[ "$runtime" == "aspnetcore" ]]; then
-        version_file_url="$azure_feed/aspnetcore/Runtime/$channel/latest.version"
-    elif [ -z "$runtime" ]; then
-         version_file_url="$azure_feed/Sdk/$channel/latest.version"
-    else
-        say_err "Invalid value for \$runtime"
-        return 1
-    fi
-    say_verbose "get_version_from_latestversion_file: latest url: $version_file_url"
-
-    download "$version_file_url" || return $?
-    return 0
-}
-
-# args:
-# json_file - $1
-parse_globaljson_file_for_version() {
-    eval $invocation
-
-    local json_file="$1"
-    if [ ! -f "$json_file" ]; then
-        say_err "Unable to find \`$json_file\`"
-        return 1
-    fi
-
-    sdk_section=$(cat "$json_file" | tr -d "\r" | awk '/"sdk"/,/}/')
-    if [ -z "$sdk_section" ]; then
-        say_err "Unable to parse the SDK node in \`$json_file\`"
-        return 1
-    fi
-
-    sdk_list=$(echo $sdk_section | awk -F"[{}]" '{print $2}')
-    sdk_list=${sdk_list//[\" ]/}
-    sdk_list=${sdk_list//,/$'\n'}
-
-    local version_info=""
-    while read -r line; do
-      IFS=:
-      while read -r key value; do
-        if [[ "$key" == "version" ]]; then
-          version_info=$value
-        fi
-      done <<< "$line"
-    done <<< "$sdk_list"
-    if [ -z "$version_info" ]; then
-        say_err "Unable to find the SDK:version node in \`$json_file\`"
-        return 1
-    fi
-
-    unset IFS;
-    echo "$version_info"
-    return 0
-}
-
-# args:
-# azure_feed - $1
-# channel - $2
-# normalized_architecture - $3
-# version - $4
-# json_file - $5
-get_specific_version_from_version() {
-    eval $invocation
-
-    local azure_feed="$1"
-    local channel="$2"
-    local normalized_architecture="$3"
-    local version="$(to_lowercase "$4")"
-    local json_file="$5"
-
-    if [ -z "$json_file" ]; then
-        if [[ "$version" == "latest" ]]; then
-            local version_info
-            version_info="$(get_version_from_latestversion_file "$azure_feed" "$channel" "$normalized_architecture" false)" || return 1
-            say_verbose "get_specific_version_from_version: version_info=$version_info"
-            echo "$version_info" | get_version_from_latestversion_file_content
-            return 0
-        else
-            echo "$version"
-            return 0
-        fi
-    else
-        local version_info
-        version_info="$(parse_globaljson_file_for_version "$json_file")" || return 1
-        echo "$version_info"
-        return 0
-    fi
-}
-
-# args:
-# azure_feed - $1
-# channel - $2
-# normalized_architecture - $3
-# specific_version - $4
-# normalized_os - $5
-construct_download_link() {
-    eval $invocation
-
-    local azure_feed="$1"
-    local channel="$2"
-    local normalized_architecture="$3"
-    local specific_version="${4//[$'\t\r\n']}"
-    local specific_product_version="$(get_specific_product_version "$1" "$4")"
-    local osname="$5"
-
-    local download_link=null
-    if [[ "$runtime" == "dotnet" ]]; then
-        download_link="$azure_feed/Runtime/$specific_version/dotnet-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz"
-    elif [[ "$runtime" == "aspnetcore" ]]; then
-        download_link="$azure_feed/aspnetcore/Runtime/$specific_version/aspnetcore-runtime-$specific_product_version-$osname-$normalized_architecture.tar.gz"
-    elif [ -z "$runtime" ]; then
-        download_link="$azure_feed/Sdk/$specific_version/dotnet-sdk-$specific_product_version-$osname-$normalized_architecture.tar.gz"
-    else
-        return 1
-    fi
-
-    echo "$download_link"
-    return 0
-}
-
-# args:
-# azure_feed - $1
-# specific_version - $2
-# download link - $3 (optional)
-get_specific_product_version() {
-    # If we find a 'productVersion.txt' at the root of any folder, we'll use its contents
-    # to resolve the version of what's in the folder, superseding the specified version.
-    # if 'productVersion.txt' is missing but download link is already available, product version will be taken from download link
-    eval $invocation
-
-    local azure_feed="$1"
-    local specific_version="${2//[$'\t\r\n']}"
-    local package_download_link=""
-    if [ $# -gt 2  ]; then
-        local package_download_link="$3"
-    fi
-    local specific_product_version=null
-
-    # Try to get the version number, using the productVersion.txt file located next to the installer file.
-    local download_links=($(get_specific_product_version_url "$azure_feed" "$specific_version" true "$package_download_link")
-        $(get_specific_product_version_url "$azure_feed" "$specific_version" false "$package_download_link"))
-
-    for download_link in "${download_links[@]}"
-    do
-        say_verbose "Checking for the existence of $download_link"
-
-        if machine_has "curl"
-        then
-            if ! specific_product_version=$(curl -sL --fail "${download_link}${feed_credential}" 2>&1); then
-                continue
-            else
-                echo "${specific_product_version//[$'\t\r\n']}"
-                return 0
-            fi
-
-        elif machine_has "wget"
-        then
-            specific_product_version=$(wget -qO- "${download_link}${feed_credential}" 2>&1)
-            if [ $? = 0 ]; then
-                echo "${specific_product_version//[$'\t\r\n']}"
-                return 0
-            fi
-        fi
-    done
-    
-    # Getting the version number with productVersion.txt has failed. Try parsing the download link for a version number.
-    say_verbose "Failed to get the version using productVersion.txt file. Download link will be parsed instead."
-    specific_product_version="$(get_product_specific_version_from_download_link "$package_download_link" "$specific_version")"
-    echo "${specific_product_version//[$'\t\r\n']}"
-    return 0
-}
-
-# args:
-# azure_feed - $1
-# specific_version - $2
-# is_flattened - $3
-# download link - $4 (optional)
-get_specific_product_version_url() {
-    eval $invocation
-
-    local azure_feed="$1"
-    local specific_version="$2"
-    local is_flattened="$3"
-    local package_download_link=""
-    if [ $# -gt 3  ]; then
-        local package_download_link="$4"
-    fi
-
-    local pvFileName="productVersion.txt"
-    if [ "$is_flattened" = true ]; then
-        if [ -z "$runtime" ]; then
-            pvFileName="sdk-productVersion.txt"
-        elif [[ "$runtime" == "dotnet" ]]; then
-            pvFileName="runtime-productVersion.txt"
-        else
-            pvFileName="$runtime-productVersion.txt"
-        fi
-    fi
-
-    local download_link=null
-
-    if [ -z "$package_download_link" ]; then
-        if [[ "$runtime" == "dotnet" ]]; then
-            download_link="$azure_feed/Runtime/$specific_version/${pvFileName}"
-        elif [[ "$runtime" == "aspnetcore" ]]; then
-            download_link="$azure_feed/aspnetcore/Runtime/$specific_version/${pvFileName}"
-        elif [ -z "$runtime" ]; then
-            download_link="$azure_feed/Sdk/$specific_version/${pvFileName}"
-        else
-            return 1
-        fi
-    else
-        download_link="${package_download_link%/*}/${pvFileName}"
-    fi
-
-    say_verbose "Constructed productVersion link: $download_link"
-    echo "$download_link"
-    return 0
-}
-
-# args:
-# download link - $1
-# specific version - $2
-get_product_specific_version_from_download_link()
-{
-    eval $invocation
-
-    local download_link="$1"
-    local specific_version="$2"
-    local specific_product_version="" 
-
-    if [ -z "$download_link" ]; then
-        echo "$specific_version"
-        return 0
-    fi
-
-    #get filename
-    filename="${download_link##*/}"
-
-    #product specific version follows the product name
-    #for filename 'dotnet-sdk-3.1.404-linux-x64.tar.gz': the product version is 3.1.404
-    IFS='-'
-    read -ra filename_elems <<< "$filename"
-    count=${#filename_elems[@]}
-    if [[ "$count" -gt 2 ]]; then
-        specific_product_version="${filename_elems[2]}"
-    else
-        specific_product_version=$specific_version
-    fi
-    unset IFS;
-    echo "$specific_product_version"
-    return 0
-}
-
-# args:
-# azure_feed - $1
-# channel - $2
-# normalized_architecture - $3
-# specific_version - $4
-construct_legacy_download_link() {
-    eval $invocation
-
-    local azure_feed="$1"
-    local channel="$2"
-    local normalized_architecture="$3"
-    local specific_version="${4//[$'\t\r\n']}"
-
-    local distro_specific_osname
-    distro_specific_osname="$(get_legacy_os_name)" || return 1
-
-    local legacy_download_link=null
-    if [[ "$runtime" == "dotnet" ]]; then
-        legacy_download_link="$azure_feed/Runtime/$specific_version/dotnet-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz"
-    elif [ -z "$runtime" ]; then
-        legacy_download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$distro_specific_osname-$normalized_architecture.$specific_version.tar.gz"
-    else
-        return 1
-    fi
-
-    echo "$legacy_download_link"
-    return 0
-}
-
-get_user_install_path() {
-    eval $invocation
-
-    if [ ! -z "${DOTNET_INSTALL_DIR:-}" ]; then
-        echo "$DOTNET_INSTALL_DIR"
-    else
-        echo "$HOME/.dotnet"
-    fi
-    return 0
-}
-
-# args:
-# install_dir - $1
-resolve_installation_path() {
-    eval $invocation
-
-    local install_dir=$1
-    if [ "$install_dir" = "<auto>" ]; then
-        local user_install_path="$(get_user_install_path)"
-        say_verbose "resolve_installation_path: user_install_path=$user_install_path"
-        echo "$user_install_path"
-        return 0
-    fi
-
-    echo "$install_dir"
-    return 0
-}
-
-# args:
-# relative_or_absolute_path - $1
-get_absolute_path() {
-    eval $invocation
-
-    local relative_or_absolute_path=$1
-    echo "$(cd "$(dirname "$1")" && pwd -P)/$(basename "$1")"
-    return 0
-}
-
-# args:
-# override - $1 (boolean, true or false)
-get_cp_options() {
-    eval $invocation
-
-    local override="$1"
-    local override_switch=""
-
-    if [ "$override" = false ]; then
-        override_switch="-n"
-
-        # create temporary files to check if 'cp -u' is supported
-        tmp_dir="$(mktemp -d)"
-        tmp_file="$tmp_dir/testfile"
-        tmp_file2="$tmp_dir/testfile2"
-
-        touch "$tmp_file"
-
-        # use -u instead of -n if it's available
-        if cp -u "$tmp_file" "$tmp_file2" 2>/dev/null; then
-            override_switch="-u"
-        fi
-
-        # clean up
-        rm -f "$tmp_file" "$tmp_file2"
-        rm -rf "$tmp_dir"
-    fi
-
-    echo "$override_switch"
-}
-
-# args:
-# input_files - stdin
-# root_path - $1
-# out_path - $2
-# override - $3
-copy_files_or_dirs_from_list() {
-    eval $invocation
-
-    local root_path="$(remove_trailing_slash "$1")"
-    local out_path="$(remove_trailing_slash "$2")"
-    local override="$3"
-    local override_switch="$(get_cp_options "$override")"
-
-    cat | uniq | while read -r file_path; do
-        local path="$(remove_beginning_slash "${file_path#$root_path}")"
-        local target="$out_path/$path"
-        if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ] || [ -L "$target" ])); then
-            mkdir -p "$out_path/$(dirname "$path")"
-            if [ -d "$target" ] || [ -L "$target" ]; then
-                rm -rf "$target"
-            fi
-            cp -RP $override_switch "$root_path/$path" "$target"
-        fi
-    done
-}
-
-# args:
-# zip_uri - $1
-get_remote_file_size() {
-    local zip_uri="$1"
-
-    if machine_has "curl"; then
-        file_size=$(curl -sI  "$zip_uri" | grep -i content-length | awk '{ num = $2 + 0; print num }')
-    elif machine_has "wget"; then
-        file_size=$(wget --spider --server-response -O /dev/null "$zip_uri" 2>&1 | grep -i 'Content-Length:' | awk '{ num = $2 + 0; print num }')
-    else
-        say "Neither curl nor wget is available on this system."
-        return
-    fi
-
-    if [ -n "$file_size" ]; then
-        say "Remote file $zip_uri size is $file_size bytes."
-        echo "$file_size"
-    else
-        say_verbose "Content-Length header was not extracted for $zip_uri."
-        echo ""
-    fi
-}
-
-# args:
-# zip_path - $1
-# out_path - $2
-# remote_file_size - $3
-extract_dotnet_package() {
-    eval $invocation
-
-    local zip_path="$1"
-    local out_path="$2"
-    local remote_file_size="$3"
-
-    local temp_out_path="$(mktemp -d "$temporary_file_template")"
-
-    local failed=false
-    tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true
-
-    local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/'
-    find "$temp_out_path" \( -type f -o -type l \) | grep -Eo "$folders_with_version_regex" | sort | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" false
-    find "$temp_out_path" \( -type f -o -type l \) | grep -Ev "$folders_with_version_regex" | copy_files_or_dirs_from_list "$temp_out_path" "$out_path" "$override_non_versioned_files"
-    
-    validate_remote_local_file_sizes "$zip_path" "$remote_file_size"
-    
-    rm -rf "$temp_out_path"
-    if [ -z ${keep_zip+x} ]; then
-        rm -f "$zip_path" && say_verbose "Temporary archive file $zip_path was removed"
-    fi
-
-    if [ "$failed" = true ]; then
-        say_err "Extraction failed"
-        return 1
-    fi
-    return 0
-}
-
-# args:
-# remote_path - $1
-# disable_feed_credential - $2
-get_http_header()
-{
-    eval $invocation
-    local remote_path="$1"
-    local disable_feed_credential="$2"
-
-    local failed=false
-    local response
-    if machine_has "curl"; then
-        get_http_header_curl $remote_path $disable_feed_credential || failed=true
-    elif machine_has "wget"; then
-        get_http_header_wget $remote_path $disable_feed_credential || failed=true
-    else
-        failed=true
-    fi
-    if [ "$failed" = true ]; then
-        say_verbose "Failed to get HTTP header: '$remote_path'."
-        return 1
-    fi
-    return 0
-}
-
-# args:
-# remote_path - $1
-# disable_feed_credential - $2
-get_http_header_curl() {
-    eval $invocation
-    local remote_path="$1"
-    local disable_feed_credential="$2"
-
-    remote_path_with_credential="$remote_path"
-    if [ "$disable_feed_credential" = false ]; then
-        remote_path_with_credential+="$feed_credential"
-    fi
-
-    curl_options="-I -sSL --retry 5 --retry-delay 2 --connect-timeout 15 "
-    curl $curl_options "$remote_path_with_credential" 2>&1 || return 1
-    return 0
-}
-
-# args:
-# remote_path - $1
-# disable_feed_credential - $2
-get_http_header_wget() {
-    eval $invocation
-    local remote_path="$1"
-    local disable_feed_credential="$2"
-    local wget_options="-q -S --spider --tries 5 "
-
-    local wget_options_extra=''
-
-    # Test for options that aren't supported on all wget implementations.
-    if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then
-        wget_options_extra="--waitretry 2 --connect-timeout 15 "
-    else
-        say "wget extra options are unavailable for this environment"
-    fi
-
-    remote_path_with_credential="$remote_path"
-    if [ "$disable_feed_credential" = false ]; then
-        remote_path_with_credential+="$feed_credential"
-    fi
-
-    wget $wget_options $wget_options_extra "$remote_path_with_credential" 2>&1
-
-    return $?
-}
-
-# args:
-# remote_path - $1
-# [out_path] - $2 - stdout if not provided
-download() {
-    eval $invocation
-
-    local remote_path="$1"
-    local out_path="${2:-}"
-
-    if [[ "$remote_path" != "http"* ]]; then
-        cp "$remote_path" "$out_path"
-        return $?
-    fi
-
-    local failed=false
-    local attempts=0
-    while [ $attempts -lt 3 ]; do
-        attempts=$((attempts+1))
-        failed=false
-        if machine_has "curl"; then
-            downloadcurl "$remote_path" "$out_path" || failed=true
-        elif machine_has "wget"; then
-            downloadwget "$remote_path" "$out_path" || failed=true
-        else
-            say_err "Missing dependency: neither curl nor wget was found."
-            exit 1
-        fi
-
-        if [ "$failed" = false ] || [ $attempts -ge 3 ] || { [ -n "${http_code-}" ] && [ "${http_code}" = "404" ]; }; then
-            break
-        fi
-
-        say "Download attempt #$attempts has failed: ${http_code-} ${download_error_msg-}"
-        say "Attempt #$((attempts+1)) will start in $((attempts*10)) seconds."
-        sleep $((attempts*10))
-    done
-
-    if [ "$failed" = true ]; then
-        say_verbose "Download failed: $remote_path"
-        return 1
-    fi
-    return 0
-}
-
-# Updates global variables $http_code and $download_error_msg
-downloadcurl() {
-    eval $invocation
-    unset http_code
-    unset download_error_msg
-    local remote_path="$1"
-    local out_path="${2:-}"
-    # Append feed_credential as late as possible before calling curl to avoid logging feed_credential
-    # Avoid passing URI with credentials to functions: note, most of them echoing parameters of invocation in verbose output.
-    local remote_path_with_credential="${remote_path}${feed_credential}"
-    local curl_options="--retry 20 --retry-delay 2 --connect-timeout 15 -sSL -f --create-dirs "
-    local curl_exit_code=0;
-    if [ -z "$out_path" ]; then
-        curl_output=$(curl $curl_options "$remote_path_with_credential" 2>&1)
-        curl_exit_code=$?
-        echo "$curl_output"
-    else
-        curl_output=$(curl $curl_options -o "$out_path" "$remote_path_with_credential" 2>&1)
-        curl_exit_code=$?
-    fi
-
-    # Regression in curl causes curl with --retry to return a 0 exit code even when it fails to download a file - https://github.com/curl/curl/issues/17554
-    if [ $curl_exit_code -eq 0 ] && echo "$curl_output" | grep -q "^curl: ([0-9]*) "; then
-        curl_exit_code=$(echo "$curl_output" | sed 's/curl: (\([0-9]*\)).*/\1/')
-    fi
-
-    if [ $curl_exit_code -gt 0 ]; then
-        download_error_msg="Unable to download $remote_path."
-        # Check for curl timeout codes
-        if [[ $curl_exit_code == 7 || $curl_exit_code == 28 ]]; then
-            download_error_msg+=" Failed to reach the server: connection timeout."
-        else
-            local disable_feed_credential=false
-            local response=$(get_http_header_curl $remote_path $disable_feed_credential)
-            http_code=$( echo "$response" | awk '/^HTTP/{print $2}' | tail -1 )
-            if  [[ ! -z $http_code && $http_code != 2* ]]; then
-                download_error_msg+=" Returned HTTP status code: $http_code."
-            fi
-        fi
-        say_verbose "$download_error_msg"
-        return 1
-    fi
-    return 0
-}
-
-
-# Updates global variables $http_code and $download_error_msg
-downloadwget() {
-    eval $invocation
-    unset http_code
-    unset download_error_msg
-    local remote_path="$1"
-    local out_path="${2:-}"
-    # Append feed_credential as late as possible before calling wget to avoid logging feed_credential
-    local remote_path_with_credential="${remote_path}${feed_credential}"
-    local wget_options="--tries 20 "
-
-    local wget_options_extra=''
-    local wget_result=''
-
-    # Test for options that aren't supported on all wget implementations.
-    if [[ $(wget -h 2>&1 | grep -E 'waitretry|connect-timeout') ]]; then
-        wget_options_extra="--waitretry 2 --connect-timeout 15 "
-    else
-        say "wget extra options are unavailable for this environment"
-    fi
-
-    if [ -z "$out_path" ]; then
-        wget -q $wget_options $wget_options_extra -O - "$remote_path_with_credential" 2>&1
-        wget_result=$?
-    else
-        wget $wget_options $wget_options_extra -O "$out_path" "$remote_path_with_credential" 2>&1
-        wget_result=$?
-    fi
-
-    if [[ $wget_result != 0 ]]; then
-        local disable_feed_credential=false
-        local response=$(get_http_header_wget $remote_path $disable_feed_credential)
-        http_code=$( echo "$response" | awk '/^  HTTP/{print $2}' | tail -1 )
-        download_error_msg="Unable to download $remote_path."
-        if  [[ ! -z $http_code && $http_code != 2* ]]; then
-            download_error_msg+=" Returned HTTP status code: $http_code."
-        # wget exit code 4 stands for network-issue
-        elif [[ $wget_result == 4 ]]; then
-            download_error_msg+=" Failed to reach the server: connection timeout."
-        fi
-        say_verbose "$download_error_msg"
-        return 1
-    fi
-
-    return 0
-}
-
-get_download_link_from_aka_ms() {
-    eval $invocation
-
-    #quality is not supported for LTS or STS channel
-    #STS maps to current
-    if [[ ! -z "$normalized_quality"  && ("$normalized_channel" == "LTS" || "$normalized_channel" == "STS") ]]; then
-        normalized_quality=""
-        say_warning "Specifying quality for STS or LTS channel is not supported, the quality will be ignored."
-    fi
-
-    say_verbose "Retrieving primary payload URL from aka.ms for channel: '$normalized_channel', quality: '$normalized_quality', product: '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'." 
-
-    #construct aka.ms link
-    aka_ms_link="https://aka.ms/dotnet"
-    if  [ "$internal" = true ]; then
-        aka_ms_link="$aka_ms_link/internal"
-    fi
-    aka_ms_link="$aka_ms_link/$normalized_channel"
-    if [[ ! -z "$normalized_quality" ]]; then
-        aka_ms_link="$aka_ms_link/$normalized_quality"
-    fi
-    aka_ms_link="$aka_ms_link/$normalized_product-$normalized_os-$normalized_architecture.tar.gz"
-    say_verbose "Constructed aka.ms link: '$aka_ms_link'."
-
-    #get HTTP response
-    #do not pass credentials as a part of the $aka_ms_link and do not apply credentials in the get_http_header function
-    #otherwise the redirect link would have credentials as well
-    #it would result in applying credentials twice to the resulting link and thus breaking it, and in echoing credentials to the output as a part of redirect link
-    disable_feed_credential=true
-    response="$(get_http_header $aka_ms_link $disable_feed_credential)"
-
-    say_verbose "Received response: $response"
-    # Get results of all the redirects.
-    http_codes=$( echo "$response" | awk '$1 ~ /^HTTP/ {print $2}' )
-    # Allow intermediate 301 redirects and tolerate proxy-injected 200s
-    broken_redirects=$( echo "$http_codes" | sed '$d' | grep -vE '^(301|200)$' )
-    # The response may end without final code 2xx/4xx/5xx somehow, e.g. network restrictions on www.bing.com causes redirecting to bing.com fails with connection refused.
-    # In this case it should not exclude the last.
-    last_http_code=$(  echo "$http_codes" | tail -n 1 )
-    if ! [[ $last_http_code =~ ^(2|4|5)[0-9][0-9]$ ]]; then
-        broken_redirects=$( echo "$http_codes" | grep -vE '^(301|200)$' )
-    fi
-
-    # All HTTP codes are 301 (Moved Permanently), the redirect link exists.
-    if [[ -z "$broken_redirects" ]]; then
-        aka_ms_download_link=$( echo "$response" | awk '$1 ~ /^Location/{print $2}' | tail -1 | tr -d '\r')
-
-        if [[ -z "$aka_ms_download_link" ]]; then
-            say_verbose "The aka.ms link '$aka_ms_link' is not valid: failed to get redirect location."
-            return 1
-        fi
-
-        say_verbose "The redirect location retrieved: '$aka_ms_download_link'."
-        return 0
-    else
-        say_verbose "The aka.ms link '$aka_ms_link' is not valid: received HTTP code: $(echo "$broken_redirects" | paste -sd "," -)."
-        return 1
-    fi
-}
-
-get_feeds_to_use()
-{
-    feeds=(
-    "https://builds.dotnet.microsoft.com/dotnet"
-    "https://ci.dot.net/public"
-    )
-
-    if [[ -n "$azure_feed" ]]; then
-        feeds=("$azure_feed")
-    fi
-
-    if [[ -n "$uncached_feed" ]]; then
-        feeds=("$uncached_feed")
-    fi
-}
-
-# THIS FUNCTION MAY EXIT (if the determined version is already installed).
-generate_download_links() {
-
-    download_links=()
-    specific_versions=()
-    effective_versions=()
-    link_types=()
-
-    # If generate_akams_links returns false, no fallback to old links. Just terminate.
-    # This function may also 'exit' (if the determined version is already installed).
-    generate_akams_links || return
-
-    # Check other feeds only if we haven't been able to find an aka.ms link.
-    if [[ "${#download_links[@]}" -lt 1 ]]; then
-        for feed in ${feeds[@]}
-        do
-            # generate_regular_links may also 'exit' (if the determined version is already installed).
-            generate_regular_links $feed || return
-        done
-    fi
-
-    if [[ "${#download_links[@]}" -eq 0 ]]; then
-        say_err "Failed to resolve the exact version number."
-        return 1
-    fi
-
-    say_verbose "Generated ${#download_links[@]} links."
-    for link_index in ${!download_links[@]}
-    do
-        say_verbose "Link $link_index: ${link_types[$link_index]}, ${effective_versions[$link_index]}, ${download_links[$link_index]}"
-    done
-}
-
-# THIS FUNCTION MAY EXIT (if the determined version is already installed).
-generate_akams_links() {
-    local valid_aka_ms_link=true;
-
-    normalized_version="$(to_lowercase "$version")"
-    if [[ "$normalized_version" != "latest" ]] && [ -n "$normalized_quality" ]; then
-        say_err "Quality and Version options are not allowed to be specified simultaneously. See https://learn.microsoft.com/dotnet/core/tools/dotnet-install-script#options for details."
-        return 1
-    fi
-
-    if [[ -n "$json_file" || "$normalized_version" != "latest" ]]; then
-        # aka.ms links are not needed when exact version is specified via command or json file
-        return
-    fi
-
-    get_download_link_from_aka_ms || valid_aka_ms_link=false
-
-    if [[ "$valid_aka_ms_link" == true ]]; then
-        say_verbose "Retrieved primary payload URL from aka.ms link: '$aka_ms_download_link'."
-        say_verbose "Downloading using legacy url will not be attempted."
-
-        download_link=$aka_ms_download_link
-
-        #get version from the path
-        IFS='/'
-        read -ra pathElems <<< "$download_link"
-        count=${#pathElems[@]}
-        specific_version="${pathElems[count-2]}"
-        unset IFS;
-        say_verbose "Version: '$specific_version'."
-
-        #Retrieve effective version
-        effective_version="$(get_specific_product_version "$azure_feed" "$specific_version" "$download_link")"
-
-        # Add link info to arrays
-        download_links+=($download_link)
-        specific_versions+=($specific_version)
-        effective_versions+=($effective_version)
-        link_types+=("aka.ms")
-
-        #  Check if the SDK version is already installed.
-        if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then
-            say "$asset_name with version '$effective_version' is already installed."
-            exit 0
-        fi
-
-        return 0
-    fi
-
-    # if quality is specified - exit with error - there is no fallback approach
-    if [ ! -z "$normalized_quality" ]; then
-        say_err "Failed to locate the latest version in the channel '$normalized_channel' with '$normalized_quality' quality for '$normalized_product', os: '$normalized_os', architecture: '$normalized_architecture'."
-        say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support."
-        return 1
-    fi
-    say_verbose "Falling back to latest.version file approach."
-}
-
-# THIS FUNCTION MAY EXIT (if the determined version is already installed)
-# args:
-# feed - $1
-generate_regular_links() {
-    local feed="$1"
-    local valid_legacy_download_link=true
-
-    specific_version=$(get_specific_version_from_version "$feed" "$channel" "$normalized_architecture" "$version" "$json_file") || specific_version='0'
-
-    if [[ "$specific_version" == '0' ]]; then
-        say_verbose "Failed to resolve the specific version number using feed '$feed'"
-        return
-    fi
-
-    effective_version="$(get_specific_product_version "$feed" "$specific_version")"
-    say_verbose "specific_version=$specific_version"
-
-    download_link="$(construct_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version" "$normalized_os")"
-    say_verbose "Constructed primary named payload URL: $download_link"
-
-    # Add link info to arrays
-    download_links+=($download_link)
-    specific_versions+=($specific_version)
-    effective_versions+=($effective_version)
-    link_types+=("primary")
-
-    legacy_download_link="$(construct_legacy_download_link "$feed" "$channel" "$normalized_architecture" "$specific_version")" || valid_legacy_download_link=false
-
-    if [ "$valid_legacy_download_link" = true ]; then
-        say_verbose "Constructed legacy named payload URL: $legacy_download_link"
-    
-        download_links+=($legacy_download_link)
-        specific_versions+=($specific_version)
-        effective_versions+=($effective_version)
-        link_types+=("legacy")
-    else
-        legacy_download_link=""
-        say_verbose "Could not construct a legacy_download_link; omitting..."
-    fi
-
-    #  Check if the SDK version is already installed.
-    if [[ "$dry_run" != true ]] && is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then
-        say "$asset_name with version '$effective_version' is already installed."
-        exit 0
-    fi
-}
-
-print_dry_run() {
-
-    say "Payload URLs:"
-
-    for link_index in "${!download_links[@]}"
-        do
-            say "URL #$link_index - ${link_types[$link_index]}: ${download_links[$link_index]}"
-    done
-
-    resolved_version=${specific_versions[0]}
-    repeatable_command="./$script_name --version "\""$resolved_version"\"" --install-dir "\""$install_root"\"" --architecture "\""$normalized_architecture"\"" --os "\""$normalized_os"\"""
-    
-    if [ ! -z "$normalized_quality" ]; then
-        repeatable_command+=" --quality "\""$normalized_quality"\"""
-    fi
-
-    if [[ "$runtime" == "dotnet" ]]; then
-        repeatable_command+=" --runtime "\""dotnet"\"""
-    elif [[ "$runtime" == "aspnetcore" ]]; then
-        repeatable_command+=" --runtime "\""aspnetcore"\"""
-    fi
-
-    repeatable_command+="$non_dynamic_parameters"
-
-    if [ -n "$feed_credential" ]; then
-        repeatable_command+=" --feed-credential "\""<feed_credential>"\"""
-    fi
-
-    say "Repeatable invocation: $repeatable_command"
-}
-
-calculate_vars() {
-    eval $invocation
-
-    script_name=$(basename "$0")
-    normalized_architecture="$(get_normalized_architecture_from_architecture "$architecture")"
-    say_verbose "Normalized architecture: '$normalized_architecture'."
-    normalized_os="$(get_normalized_os "$user_defined_os")"
-    say_verbose "Normalized OS: '$normalized_os'."
-    normalized_quality="$(get_normalized_quality "$quality")"
-    say_verbose "Normalized quality: '$normalized_quality'."
-    normalized_channel="$(get_normalized_channel "$channel")"
-    say_verbose "Normalized channel: '$normalized_channel'."
-    normalized_product="$(get_normalized_product "$runtime")"
-    say_verbose "Normalized product: '$normalized_product'."
-    install_root="$(resolve_installation_path "$install_dir")"
-    say_verbose "InstallRoot: '$install_root'."
-
-    normalized_architecture="$(get_normalized_architecture_for_specific_sdk_version "$version" "$normalized_channel" "$normalized_architecture")"
-
-    if [[ "$runtime" == "dotnet" ]]; then
-        asset_relative_path="shared/Microsoft.NETCore.App"
-        asset_name=".NET Core Runtime"
-    elif [[ "$runtime" == "aspnetcore" ]]; then
-        asset_relative_path="shared/Microsoft.AspNetCore.App"
-        asset_name="ASP.NET Core Runtime"
-    elif [ -z "$runtime" ]; then
-        asset_relative_path="sdk"
-        asset_name=".NET Core SDK"
-    fi
-
-    get_feeds_to_use
-}
-
-install_dotnet() {
-    eval $invocation
-    local download_failed=false
-    local download_completed=false
-    local remote_file_size=0
-
-    mkdir -p "$install_root"
-    zip_path="${zip_path:-$(mktemp "$temporary_file_template")}"
-    say_verbose "Archive path: $zip_path"
-
-    for link_index in "${!download_links[@]}"
-    do
-        download_link="${download_links[$link_index]}"
-        specific_version="${specific_versions[$link_index]}"
-        effective_version="${effective_versions[$link_index]}"
-        link_type="${link_types[$link_index]}"
-
-        say "Attempting to download using $link_type link $download_link"
-
-        # The download function will set variables $http_code and $download_error_msg in case of failure.
-        download_failed=false
-        download "$download_link" "$zip_path" 2>&1 || download_failed=true
-
-        if [ "$download_failed" = true ]; then
-            case ${http_code-} in
-            404)
-                say "The resource at $link_type link '$download_link' is not available."
-                ;;
-            *)
-                say "Failed to download $link_type link '$download_link': ${http_code-} ${download_error_msg-}"
-                ;;
-            esac
-            rm -f "$zip_path" 2>&1 && say_verbose "Temporary archive file $zip_path was removed"
-        else
-            download_completed=true
-            break
-        fi
-    done
-
-    if [[ "$download_completed" == false ]]; then
-        say_err "Could not find \`$asset_name\` with version = $specific_version"
-        say_err "Refer to: https://aka.ms/dotnet-os-lifecycle for information on .NET Core support"
-        return 1
-    fi
-
-    remote_file_size="$(get_remote_file_size "$download_link")"
-
-    say "Extracting archive from $download_link"
-    extract_dotnet_package "$zip_path" "$install_root" "$remote_file_size" || return 1
-
-    #  Check if the SDK version is installed; if not, fail the installation.
-    # if the version contains "RTM" or "servicing"; check if a 'release-type' SDK version is installed.
-    if [[ $specific_version == *"rtm"* || $specific_version == *"servicing"* ]]; then
-        IFS='-'
-        read -ra verArr <<< "$specific_version"
-        release_version="${verArr[0]}"
-        unset IFS;
-        say_verbose "Checking installation: version = $release_version"
-        if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$release_version"; then
-            say "Installed version is $effective_version"
-            return 0
-        fi
-    fi
-
-    #  Check if the standard SDK version is installed.
-    say_verbose "Checking installation: version = $effective_version"
-    if is_dotnet_package_installed "$install_root" "$asset_relative_path" "$effective_version"; then
-        say "Installed version is $effective_version"
-        return 0
-    fi
-
-    # Version verification failed. More likely something is wrong either with the downloaded content or with the verification algorithm.
-    say_err "Failed to verify the version of installed \`$asset_name\`.\nInstallation source: $download_link.\nInstallation location: $install_root.\nReport the bug at https://github.com/dotnet/install-scripts/issues."
-    say_err "\`$asset_name\` with version = $effective_version failed to install with an error."
-    return 1
-}
-
-args=("$@")
-
-local_version_file_relative_path="/.version"
-bin_folder_relative_path=""
-temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX"
-
-channel="LTS"
-version="Latest"
-json_file=""
-install_dir="<auto>"
-architecture="<auto>"
-dry_run=false
-no_path=false
-azure_feed=""
-uncached_feed=""
-feed_credential=""
-verbose=false
-runtime=""
-runtime_id=""
-quality=""
-internal=false
-override_non_versioned_files=true
-non_dynamic_parameters=""
-user_defined_os=""
-
-while [ $# -ne 0 ]
-do
-    name="$1"
-    case "$name" in
-        -c|--channel|-[Cc]hannel)
-            shift
-            channel="$1"
-            ;;
-        -v|--version|-[Vv]ersion)
-            shift
-            version="$1"
-            ;;
-        -q|--quality|-[Qq]uality)
-            shift
-            quality="$1"
-            ;;
-        --internal|-[Ii]nternal)
-            internal=true
-            non_dynamic_parameters+=" $name"
-            ;;
-        -i|--install-dir|-[Ii]nstall[Dd]ir)
-            shift
-            install_dir="$1"
-            ;;
-        --arch|--architecture|-[Aa]rch|-[Aa]rchitecture)
-            shift
-            architecture="$1"
-            ;;
-        --os|-[Oo][SS])
-            shift
-            user_defined_os="$1"
-            ;;
-        --shared-runtime|-[Ss]hared[Rr]untime)
-            say_warning "The --shared-runtime flag is obsolete and may be removed in a future version of this script. The recommended usage is to specify '--runtime dotnet'."
-            if [ -z "$runtime" ]; then
-                runtime="dotnet"
-            fi
-            ;;
-        --runtime|-[Rr]untime)
-            shift
-            runtime="$1"
-            if [[ "$runtime" != "dotnet" ]] && [[ "$runtime" != "aspnetcore" ]]; then
-                say_err "Unsupported value for --runtime: '$1'. Valid values are 'dotnet' and 'aspnetcore'."
-                if [[ "$runtime" == "windowsdesktop" ]]; then
-                    say_err "WindowsDesktop archives are manufactured for Windows platforms only."
-                fi
-                exit 1
-            fi
-            ;;
-        --dry-run|-[Dd]ry[Rr]un)
-            dry_run=true
-            ;;
-        --no-path|-[Nn]o[Pp]ath)
-            no_path=true
-            non_dynamic_parameters+=" $name"
-            ;;
-        --verbose|-[Vv]erbose)
-            verbose=true
-            non_dynamic_parameters+=" $name"
-            ;;
-        --azure-feed|-[Aa]zure[Ff]eed)
-            shift
-            azure_feed="$1"
-            non_dynamic_parameters+=" $name "\""$1"\"""
-            ;;
-        --uncached-feed|-[Uu]ncached[Ff]eed)
-            shift
-            uncached_feed="$1"
-            non_dynamic_parameters+=" $name "\""$1"\"""
-            ;;
-        --feed-credential|-[Ff]eed[Cc]redential)
-            shift
-            feed_credential="$1"
-            #feed_credential should start with "?", for it to be added to the end of the link.
-            #adding "?" at the beginning of the feed_credential if needed.
-            [[ -z "$(echo $feed_credential)" ]] || [[ $feed_credential == \?* ]] || feed_credential="?$feed_credential"
-            ;;
-        --runtime-id|-[Rr]untime[Ii]d)
-            shift
-            runtime_id="$1"
-            non_dynamic_parameters+=" $name "\""$1"\"""
-            say_warning "Use of --runtime-id is obsolete and should be limited to the versions below 2.1. To override architecture, use --architecture option instead. To override OS, use --os option instead."
-            ;;
-        --jsonfile|-[Jj][Ss]on[Ff]ile)
-            shift
-            json_file="$1"
-            ;;
-        --skip-non-versioned-files|-[Ss]kip[Nn]on[Vv]ersioned[Ff]iles)
-            override_non_versioned_files=false
-            non_dynamic_parameters+=" $name"
-            ;;
-        --keep-zip|-[Kk]eep[Zz]ip)
-            keep_zip=true
-            non_dynamic_parameters+=" $name"
-            ;;
-        --zip-path|-[Zz]ip[Pp]ath)
-            shift
-            zip_path="$1"
-            ;;
-        -?|--?|-h|--help|-[Hh]elp)
-            script_name="dotnet-install.sh"
-            echo ".NET Tools Installer"
-            echo "Usage:"
-            echo "       # Install a .NET SDK of a given Quality from a given Channel"
-            echo "       $script_name [-c|--channel <CHANNEL>] [-q|--quality <QUALITY>]"
-            echo "       # Install a .NET SDK of a specific public version"
-            echo "       $script_name [-v|--version <VERSION>]"
-            echo "       $script_name -h|-?|--help"
-            echo ""
-            echo "$script_name is a simple command line interface for obtaining dotnet cli."
-            echo "    Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:"
-            echo "    - The SDK needs to be installed without user interaction and without admin rights."
-            echo "    - The SDK installation doesn't need to persist across multiple CI runs."
-            echo "    To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer."
-            echo ""
-            echo "Options:"
-            echo "  -c,--channel <CHANNEL>         Download from the channel specified, Defaults to \`$channel\`."
-            echo "      -Channel"
-            echo "          Possible values:"
-            echo "          - STS - the most recent Standard Term Support release"
-            echo "          - LTS - the most recent Long Term Support release"
-            echo "          - 2-part version in a format A.B - represents a specific release"
-            echo "              examples: 2.0; 1.0"
-            echo "          - 3-part version in a format A.B.Cxx - represents a specific SDK release"
-            echo "              examples: 5.0.1xx, 5.0.2xx."
-            echo "              Supported since 5.0 release"
-            echo "          Warning: Value 'Current' is deprecated for the Channel parameter. Use 'STS' instead."
-            echo "          Note: The version parameter overrides the channel parameter when any version other than 'latest' is used."
-            echo "  -v,--version <VERSION>         Use specific VERSION, Defaults to \`$version\`."
-            echo "      -Version"
-            echo "          Possible values:"
-            echo "          - latest - the latest build on specific channel"
-            echo "          - 3-part version in a format A.B.C - represents specific version of build"
-            echo "              examples: 2.0.0-preview2-006120; 1.1.0"
-            echo "  -q,--quality <quality>         Download the latest build of specified quality in the channel."
-            echo "      -Quality"
-            echo "          The possible values are: daily, preview, GA."
-            echo "          Works only in combination with channel. Not applicable for STS and LTS channels and will be ignored if those channels are used." 
-            echo "          Supported since 5.0 release." 
-            echo "          Note: The version parameter overrides the channel parameter when any version other than 'latest' is used, and therefore overrides the quality."
-            echo "  --internal,-Internal               Download internal builds. Requires providing credentials via --feed-credential parameter."
-            echo "  --feed-credential <FEEDCREDENTIAL> Token to access Azure feed. Used as a query string to append to the Azure feed."
-            echo "      -FeedCredential                This parameter typically is not specified."
-            echo "  -i,--install-dir <DIR>             Install under specified location (see Install Location below)"
-            echo "      -InstallDir"
-            echo "  --architecture <ARCHITECTURE>      Architecture of dotnet binaries to be installed, Defaults to \`$architecture\`."
-            echo "      --arch,-Architecture,-Arch"
-            echo "          Possible values: x64, arm, arm64, s390x, ppc64le and loongarch64"
-            echo "  --os <system>                    Specifies operating system to be used when selecting the installer."
-            echo "          Overrides the OS determination approach used by the script. Supported values: osx, linux, linux-musl, freebsd, rhel.6."
-            echo "          In case any other value is provided, the platform will be determined by the script based on machine configuration."
-            echo "          Not supported for legacy links. Use --runtime-id to specify platform for legacy links."
-            echo "          Refer to: https://aka.ms/dotnet-os-lifecycle for more information."
-            echo "  --runtime <RUNTIME>                Installs a shared runtime only, without the SDK."
-            echo "      -Runtime"
-            echo "          Possible values:"
-            echo "          - dotnet     - the Microsoft.NETCore.App shared runtime"
-            echo "          - aspnetcore - the Microsoft.AspNetCore.App shared runtime"
-            echo "  --dry-run,-DryRun                  Do not perform installation. Display download link."
-            echo "  --no-path, -NoPath                 Do not set PATH for the current process."
-            echo "  --verbose,-Verbose                 Display diagnostics information."
-            echo "  --azure-feed,-AzureFeed            For internal use only."
-            echo "                                     Allows using a different storage to download SDK archives from."
-            echo "  --uncached-feed,-UncachedFeed      For internal use only."
-            echo "                                     Allows using a different storage to download SDK archives from."
-            echo "  --skip-non-versioned-files         Skips non-versioned files if they already exist, such as the dotnet executable."
-            echo "      -SkipNonVersionedFiles"
-            echo "  --jsonfile <JSONFILE>              Determines the SDK version from a user specified global.json file."
-            echo "                                     Note: global.json must have a value for 'SDK:Version'"
-            echo "  --keep-zip,-KeepZip                If set, downloaded file is kept."
-            echo "  --zip-path, -ZipPath               If set, downloaded file is stored at the specified path."
-            echo "  -?,--?,-h,--help,-Help             Shows this help message"
-            echo ""
-            echo "Install Location:"
-            echo "  Location is chosen in following order:"
-            echo "    - --install-dir option"
-            echo "    - Environmental variable DOTNET_INSTALL_DIR"
-            echo "    - $HOME/.dotnet"
-            exit 0
-            ;;
-        *)
-            say_err "Unknown argument \`$name\`"
-            exit 1
-            ;;
-    esac
-
-    shift
-done
-
-say_verbose "Note that the intended use of this script is for Continuous Integration (CI) scenarios, where:"
-say_verbose "- The SDK needs to be installed without user interaction and without admin rights."
-say_verbose "- The SDK installation doesn't need to persist across multiple CI runs."
-say_verbose "To set up a development environment or to run apps, use installers rather than this script. Visit https://dotnet.microsoft.com/download to get the installer.\n"
-
-if [ "$internal" = true ] && [ -z "$(echo $feed_credential)" ]; then
-    message="Provide credentials via --feed-credential parameter."
-    if [ "$dry_run" = true ]; then
-        say_warning "$message"
-    else
-        say_err "$message"
-        exit 1
-    fi
-fi
-
-check_min_reqs
-calculate_vars
-# generate_regular_links call below will 'exit' if the determined version is already installed.
-generate_download_links
-
-if [[ "$dry_run" = true ]]; then
-    print_dry_run
-    exit 0
-fi
-
-install_dotnet
-
-bin_path="$(get_absolute_path "$(combine_paths "$install_root" "$bin_folder_relative_path")")"
-if [ "$no_path" = false ]; then
-    say "Adding to current process PATH: \`$bin_path\`. Note: This change will be visible only when sourcing script."
-    export PATH="$bin_path":"$PATH"
-else
-    say "Binaries of dotnet can be found in $bin_path"
-fi
-
-say "Note that the script does not resolve dependencies during installation."
-say "To check the list of dependencies, go to https://learn.microsoft.com/dotnet/core/install, select your operating system and check the \"Dependencies\" section."
-say "Installation finished successfully."

+ 0 - 19
docs/publish.bat

@@ -1,19 +0,0 @@
-@echo off
-setlocal
-
-cd /d "%~dp0"
-
-if /i "%~1"=="battle" (
-    robocopy "_output.battle.server" "..\_output.battle.server" /E /COPY:DAT /DCOPY:DAT /XF Config.json libslua.so *.sh /NFL /NDL /NJH /NJS /NP
-) else (
-    robocopy "_output.server" "..\_output.server" /E /COPY:DAT /DCOPY:DAT /XD dev logfile /NFL /NDL /NJH /NJS /NP
-)
-
-rem robocopy: 0-7 = success, 8+ = error
-if errorlevel 8 (
-    echo Sync failed with error code %ERRORLEVEL%.
-    exit /b 1
-)
-
-echo Sync completed.
-exit /b 0

+ 0 - 16
docs/publish.sh

@@ -1,16 +0,0 @@
-#!/bin/bash
-
-cd "$(dirname "$0")"
-
-if [ "$1" == "battle" ]; then
-  rsync -av \
-    --exclude='Config.json' \
-    --exclude='libslua.so' \
-    --exclude='*.sh' \
-    _output.battle.server/ ../_output.battle.server/
-else
-  rsync -av \
-    --exclude='dev/' \
-    --exclude='logfile/' \
-    _output.server/ ../_output.server/
-fi

+ 0 - 404
docs/代币系统技术设计文档.md

@@ -1,404 +0,0 @@
-# 代币系统技术设计文档
-
-## 1. 需求概述
-
-新增两种道具:**代币** 和 **绑定代币**,支持以下核心功能:
-
-1. 代币/绑定代币可消耗用于购买部分付费商品(配置项控制哪些商品支持)
-2. 代币购买商品时触发累充VIP、首充、每日累充等功能
-3. 玩家可通过代币交易功能与其他玩家交换获得泪滴石
-4. 通过交易获得的代币为绑定代币,绑定代币不可再次交易
-5. 出售代币也触发累充VIP、首充、每日累充等功能
-6. 消耗比例:100个代币/绑定代币 = 1元付费货币等值商品
-
----
-
-## 2. 道具定义
-
-### 2.1 道具类型
-
-两个道具均属于**代币类(Token,ItemToolType=3)**,进入背包,通过 `RoleItemData.Items` 存储,不加入 `ItemsNotInPackage`。
-
-| 道具名称 | 建议ItemId | ItemInstanceId | 是否可交易 | 备注 |
-|---------|-----------|----------------|-----------|------|
-| 代币    | 36        | 3600           | ✅ 可交易  | 正常获得 |
-| 绑定代币 | 37        | 3700           | ❌ 不可交易 | 仅通过交易获得 |
-
-> **注意**:实际 ItemId 需对照现有 `item.xlsx` 最大值后续分配,不得与已有道具冲突。
-
-### 2.2 配置表改动(item.xlsx)
-
-新增两行:
-
-| 字段        | 代币       | 绑定代币   |
-|-----------|-----------|-----------|
-| Id        | 36        | 37        |
-| ItemType  | 3(Token) | 3(Token) |
-| ItemName  | 代币       | 绑定代币   |
-| Stack     | 99999999  | 99999999  |
-| AutoUse   | 0         | 0         |
-| HideInBag | 0         | 0         |
-| DataItemName | Token  | BoundToken |
-| ItemTypeDataName | Token | BoundToken |
-
-### 2.3 配置表改动(iteminstance.xlsx)
-
-新增两行:
-
-| 字段       | 代币  | 绑定代币 |
-|----------|------|---------|
-| InstanceId | 3600 | 3700   |
-| ItemId   | 36   | 37      |
-| ItemType | 1    | 1       |
-
-### 2.4 代码常量(ItemDefines.cs)
-
-文件:`server/src/server/OpenCards.Server.Logic/Module/Item/ItemDefines.cs`
-
-```csharp
-public static int TokenItemId = 36;         // 代币
-public static int BoundTokenItemId = 37;    // 绑定代币
-public static int TokenItemInstanceId = 3600;      // 代币捆绑实例ID
-public static int BoundTokenItemInstanceId = 3700; // 绑定代币捆绑实例ID
-```
-
----
-
-## 3. 商品代币购买功能
-
-### 3.1 配置表改动(Market.xlsx - market_gift 表)
-
-在 `Table_MarketGift` 对应的 `Market.xlsx` 的 `market_gift` sheet 新增字段:
-
-| 字段名       | 类型 | 说明 |
-|------------|-----|-----|
-| TokenEnable | int | 是否支持代币购买:0=不支持,1=支持 |
-
-对应代码改动,文件 `server/src/server/OpenCards.Server.Core/Table/Table_Market.cs`:
-
-```csharp
-/// <summary>
-/// 是否支持代币购买 0=不支持 1=支持
-/// </summary>
-public int TokenEnable;
-```
-
-### 3.2 消耗比例
-
-- 1元付费货币(CNY)= 100 代币/绑定代币
-- 下单时根据商品的 `Table_IAP.CNY` 字段计算所需代币数:`tokenCost = (int)(iap.CNY * 100)`
-
-### 3.3 代币购买流程(服务端)
-
-新增 `ChargeType.Token` 枚举值,文件 `MarketModule.cs`:
-
-```csharp
-private enum ChargeType
-{
-    Charge,
-    Ticket,
-    GMSend,
-    Token,      // 代币购买
-}
-```
-
-在下单处理 `ClientGetRechargeOrderRequest` 的处理函数中,在现有 `Ticket` 判断之后追加代币购买逻辑:
-
-```
-if (giftConfig.TokenEnable == 1)
-{
-    // 1. 计算所需代币数量
-    tokenCost = (int)(iap.CNY * 100)
-
-    // 2. 优先尝试扣代币,失败则扣绑定代币,两者都不足则返回代币不足错误码
-    // 3. 扣款成功后调用 OnPaySuccess(productId, ext1, ..., ChargeType.Token)
-    // 4. 同时调用 RecordChargeToken(iap.CNY) 记录代币消费金额
-    // 5. 返回 CODE_CHARGE_BY_TOKEN 告知客户端
-}
-```
-
-**扣款优先级**:优先扣代币,代币不足时扣绑定代币,两者数量之和不足时返回失败。
-
-在 `ClientGetRechargeOrderResponse` 中新增响应码:
-
-```csharp
-public const int CODE_CHARGE_BY_TOKEN = 4;   // 代币购买成功
-public const int CODE_TOKEN_NOT_ENOUGH = 505; // 代币不足
-public const int CODE_TOKEN_NOT_SUPPORT = 506; // 该商品不支持代币购买
-```
-
-### 3.4 代币购买对累充系统的触发
-
-在 `OnPaySuccess(ChargeType.Token)` 分支中,与 `ChargeType.Ticket` 处理方式相同:
-
-- `mRoleFlag.AddFlag(FlagDefines.ChargeFlag, convertPrice)` → 触发累计充值
-- `mRoleFlag.AddFlag(FlagDefines.UnrealRMBChargeFlag, rmbConvertPrice)` → 非真实RMB累计(不影响真实充值统计)
-- `mRoleFlag.AddFlag(FlagDefines.BuyCountFlag, 1)` → 购买次数累计
-- `CheckFosterRecharge(convertPrice)` → 触发每日累充(养成豪礼)
-- `CheckRechargeInfo()` → 触发动态难度检查
-- `GetModule<HeroModule>().AddRechargBUFData(convertPrice)` → 充值BUFF
-- `DispatchEvent(EventDefines.EventAfterPayDelivery, productId)` → 发货后事件(触发首充、VIP等检测)
-- `DispatchEvent(EventDefines.EventAfterPayDeliveryWithPrice, productId, price)` → 带价格的发货后事件
-
-> **关键**:代币购买同样走完整的 `OnPaySuccess` 流程,确保所有充值触发链均正常响应。VipExp 通过 `GiftDropID` 掉落表配置自动发放,无需单独处理。
-
-### 3.5 客户端二次确认弹窗
-
-当商品 `TokenEnable == 1` 时,客户端在点击购买按钮后弹出二次确认弹窗。
-
-**弹窗内容**(服务端下发商品信息中含 `TokenEnable` 字段,客户端据此决定是否展示):
-
-```
-标题:购买确认
-正文:该商品支持代币购买
-  · 直接充值:使用真实货币支付,立即购买
-  · 代币购买:消耗 {tokenCost} 个代币(当前拥有: {ownToken})
-[直接充值]  [代币购买]
-```
-
-**服务端协议新增**:在商品列表同步协议中,将 `TokenEnable` 和 `TokenCost` 下发给客户端。
-
----
-
-## 4. 代币交易系统
-
-### 4.1 功能概述
-
-玩家A(卖方)挂出代币,玩家B(买方)支付泪滴石,完成交易后:
-- 玩家B消耗泪滴石,获得**绑定代币**(非代币)
-- 玩家A获得泪滴石,此次出售触发累充系统
-
-绑定代币不可再次挂出交易。
-
-### 4.2 新增数据表(TokenTradeData)
-
-新增 `TokenTradeData.cs`(`OpenCards.Core/ORM/` 目录):
-
-```csharp
-[PersistType]
-public class TokenTradeOrder : ISerializable, IObjectMapping
-{
-    [PersistField(PersistStrategy.Primary)]
-    public string OrderId;          // 订单唯一ID(UUID)
-
-    [PersistField]
-    public string SellerRoleId;     // 卖方角色ID
-
-    [PersistField]
-    public int TokenAmount;         // 挂出的代币数量
-
-    [PersistField]
-    public int TearStonePrice;      // 要求的泪滴石数量
-
-    [PersistField]
-    public long CreateTime;         // 创建时间(ms时间戳)
-
-    [PersistField]
-    public long ExpireTime;         // 过期时间(ms时间戳)
-
-    [PersistField]
-    public int Status;              // 0=挂单中,1=已成交,2=已取消,3=已过期
-}
-```
-
-持久化类型常量在 `PersistenceConstants` 中新增:
-
-```csharp
-public const string TYPE_TOKEN_TRADE_ORDER = "token_trade_order";
-```
-
-### 4.3 新增协议(TokenTrade 协议文件)
-
-新建 `0x23A00.TokenTrade.cs`(`OpenCards.Core/Protocol/Client/` 目录):
-
-```
-消息码段:0x23A00 起
-```
-
-| 协议名 | 方向 | 说明 |
-|-------|-----|-----|
-| `ClientTokenTradeListRequest` | C→S | 获取交易列表 |
-| `ClientTokenTradeListResponse` | S→C | 返回交易列表 |
-| `ClientTokenTradePutRequest` | C→S | 挂出代币 |
-| `ClientTokenTradePutResponse` | S→C | 挂出结果 |
-| `ClientTokenTradeCancelRequest` | C→S | 取消挂单 |
-| `ClientTokenTradeCancelResponse` | S→C | 取消结果 |
-| `ClientTokenTradeBuyRequest` | C→S | 购买(用泪滴石换绑定代币) |
-| `ClientTokenTradeBuyResponse` | S→C | 购买结果 |
-| `ClientTokenTradeMyOrderRequest` | C→S | 查询自己的挂单 |
-| `ClientTokenTradeMyOrderResponse` | S→C | 返回自己的挂单 |
-
-**关键字段说明**:
-
-`ClientTokenTradePutRequest`:
-- `c2s_tokenAmount`:挂出代币数量
-- `c2s_tearStonePrice`:期望收取的泪滴石数量
-
-`ClientTokenTradeBuyRequest`:
-- `c2s_orderId`:目标订单ID
-
-### 4.4 新增模块(TokenTradeModule)
-
-新建 `server/src/server/OpenCards.Server.Logic/Module/TokenTradeModule.cs`。
-
-**挂单逻辑(DoTokenTradePut)**:
-
-```
-1. 校验:挂出的必须是代币(ItemId=36),不得挂绑定代币(ItemId=37)
-2. 校验代币余量是否足够
-3. 校验玩家当前挂单数量是否超上限(配置表控制,默认5单)
-4. 扣除玩家代币(EventRemoveItem,RemoveItemReason.TokenTrade)
-5. 生成 TokenTradeOrder,写入全服 Redis/DB(中心服维护订单列表)
-6. 返回成功
-```
-
-**购买逻辑(DoTokenTradeBuy)**:
-
-```
-1. 从中心服获取订单,校验订单状态=挂单中
-2. 校验买方泪滴石余量 >= 订单价格
-3. 原子操作:
-   a. 扣除买方泪滴石(EventRemoveItem,RemoveItemReason.TokenTradeBuy)
-   b. 发放买方 绑定代币(EventAddItem,ItemInstanceId=3700,AddItemReason.TokenTrade)
-   c. 向卖方发邮件,附带泪滴石奖励(EventSendMailWithItems)
-   d. 订单状态改为已成交
-4. 触发卖方出售代币的累充事件(见 4.5)
-```
-
-**取消挂单逻辑(DoTokenTradeCancel)**:
-
-```
-1. 校验订单属于本玩家且状态=挂单中
-2. 退还代币给玩家(EventAddItem,ItemInstanceId=3600,AddItemReason.TokenTradeCancel)
-3. 订单状态改为已取消
-```
-
-**定时过期**:订单过期时间由配置表控制(建议7天),服务端定时扫描过期订单,自动取消并退还代币。
-
-### 4.5 出售代币触发累充
-
-出售成交后,对**卖方**触发累充系统,等效金额按 `泪滴石数量 / 100` CNY 计算:
-
-```csharp
-float equivalentCNY = tearStoneAmount / 100f;
-long convertPrice = Convert.ToInt64(equivalentCNY * 100);
-
-// 触发累充系列
-GetModule<MarketModule>().CheckFosterRecharge(convertPrice);
-GetModule<MarketModule>().CheckRechargeInfo();
-mRoleFlag.AddFlag(FlagDefines.ChargeFlag, convertPrice, Flag.MapType.EPersist);
-mRoleFlag.AddFlag(FlagDefines.UnrealRMBChargeFlag, convertPrice, Flag.MapType.EPersist);
-mRoleFlag.AddFlag(FlagDefines.BuyCountFlag, 1, Flag.MapType.EPersist);
-DispatchEvent(EventDefines.EventAfterPayDeliveryWithPrice, 0, equivalentCNY);
-```
-
-> 出售代币不触发首充(`EventAfterPayDelivery` 不传 productId,或传 0 使相关模块跳过首充发货)。首充只在商品购买时触发,按现有逻辑执行。
-
-### 4.6 新增 AddItemReason / RemoveItemReason
-
-在 `ItemDefines.cs` 中追加:
-
-```csharp
-// AddItemReason
-public static int TokenTrade = 129;         // 代币交易获得绑定代币
-public static int TokenTradeCancel = 130;   // 代币交易取消退还代币
-
-// RemoveItemReason
-public static int TokenTrade = 60;          // 挂出代币
-public static int TokenTradeBuy = 61;       // 购买时消耗泪滴石
-```
-
----
-
-## 5. 泪滴石道具确认
-
-泪滴石为现有道具,已在英雄重置、斗技场等功能中使用(见客户端语言文件)。实现前需确认服务端 ItemId,通过查询 `item.xlsx` 获取,代币交易模块中引用该 ItemId 作为常量。
-
-```csharp
-// 待确认实际ID,填入后在 ItemDefines 中注册
-public static int TearStoneItemId = ???;
-```
-
----
-
-## 6. 涉及文件清单
-
-### 6.1 配置表
-
-| 文件 | Sheet | 操作 |
-|-----|------|-----|
-| `item.xlsx` | item | 新增代币(36)、绑定代币(37)两行 |
-| `iteminstance.xlsx` | item_instance | 新增 3600、3700 两行 |
-| `Market.xlsx` | market_gift | 新增 `TokenEnable` 字段列 |
-
-### 6.2 服务端代码
-
-| 文件 | 操作 |
-|-----|-----|
-| `OpenCards.Core/ORM/TokenTradeData.cs` | **新建**:订单数据结构 |
-| `OpenCards.Core/Protocol/Client/0x23A00.TokenTrade.cs` | **新建**:交易协议定义 |
-| `OpenCards.Core/Data/Constants.cs` | 新增消息码段常量 `TOKEN_TRADE_START` |
-| `OpenCards.Server.Logic/Module/Item/ItemDefines.cs` | 新增道具ID常量、新增 AddItemReason/RemoveItemReason |
-| `OpenCards.Server.Logic/Module/TokenTradeModule.cs` | **新建**:代币交易业务模块 |
-| `OpenCards.Server.Logic/Module/MarketModule.cs` | 新增 `ChargeType.Token`;在下单处理中追加代币购买分支;新增 `RecordChargeToken()` |
-| `OpenCards.Server.Core/Table/Table_Market.cs` | `Table_MarketGift` 新增 `TokenEnable` 字段 |
-| `OpenCards.Server.Core/Table/Table_IAP.cs` | 无需改动(复用 `CNY` 字段计算代币数) |
-| `OpenCards.GenCodec/` | 重新生成序列化代码(自动生成,运行代码生成工具) |
-| `OpenCards.Server.GenORM/` | 重新生成 ORM 代码(自动生成) |
-
-### 6.3 客户端(服务端接口侧)
-
-| 文件 | 操作 |
-|-----|-----|
-| 商品列表协议 | 在现有商品信息中新增 `TokenEnable`、`TokenCost` 字段下发 |
-| 下单协议响应 | 新增 `CODE_CHARGE_BY_TOKEN`、`CODE_TOKEN_NOT_ENOUGH` 等响应码 |
-
----
-
-## 7. 关键设计决策
-
-### 7.1 代币/绑定代币均进背包,不走 CurrencyModule
-
-代币属于代币类(`ItemToolType.Token=3`),存储在 `RoleItemData.Items`,无需改动 `CurrencyModule.cs` 和 `RoleData.cs`。查询数量、增删均通过事件 `EventGetItemCount` / `EventAddItemImp` / `EventRemoveItem` 走背包通用逻辑。
-
-### 7.2 绑定代币以独立 ItemId 区分
-
-绑定代币(ItemId=37)与代币(ItemId=36)是两个独立道具,通过 ItemId 区分是否可交易,无需额外绑定标记字段,逻辑简单清晰。
-
-### 7.3 代币购买触发累充等同于 Ticket 机制
-
-现有 `ChargeType.Ticket` 已实现"非真实充值走完整充值流程"的模式,`ChargeType.Token` 与其保持一致,复用 `OnPaySuccess` 完整逻辑,确保首充、VIP、每日累充等所有依赖充值事件的功能均正常触发。
-
-### 7.4 代币交易为全服功能
-
-代币交易订单是全服共享的(非单角色),需中心服(或独立的交易服务)维护订单列表。订单数据存储在全服 Redis/DB,而非角色本地数据。
-
-### 7.5 购买代币时扣款优先级
-
-优先消耗代币,代币不足时消耗绑定代币。两者合计不足才返回错误。逻辑在服务端实现,客户端展示合计数量。
-
----
-
-## 8. 错误码汇总
-
-| 错误码常量 | 值 | 说明 |
-|----------|---|-----|
-| `CODE_CHARGE_BY_TOKEN` | 4 | 代币购买成功(下单响应) |
-| `CODE_TOKEN_NOT_ENOUGH` | 505 | 代币不足 |
-| `CODE_TOKEN_NOT_SUPPORT` | 506 | 该商品不支持代币购买 |
-| `CODE_TRADE_ORDER_NOT_FOUND` | 507 | 交易订单不存在 |
-| `CODE_TRADE_ORDER_SOLD` | 508 | 订单已被购买 |
-| `CODE_TRADE_TOKEN_NOT_TRADEABLE` | 509 | 绑定代币不可交易 |
-| `CODE_TRADE_EXCEED_MAX_ORDER` | 510 | 超出最大挂单数量 |
-| `CODE_TRADE_TEARSTONE_NOT_ENOUGH` | 511 | 泪滴石不足 |
-
----
-
-## 9. 开发顺序建议
-
-1. **配置表**:`item.xlsx`、`iteminstance.xlsx`、`Market.xlsx` 新增字段,更新到 ServerData
-2. **基础道具**:`ItemDefines.cs` 新增常量,导表,验证背包可正常收发
-3. **商品购买**:`Table_Market.cs` 加字段 → `MarketModule.cs` 加代币购买分支 → 联调充值触发链
-4. **交易系统**:新建协议 → 新建 `TokenTradeModule` → 接入中心服订单存储 → 联调全流程
-5. **代码生成**:重新生成 GenCodec / GenORM
-6. **GM指令**:新增 GM 指令发放/扣除代币以方便测试

+ 0 - 17
docs/协议.md

@@ -1,17 +0,0 @@
-协议文件分布在以下位置:
-协议定义(手写源文件)
-客户端服务端协议(按消息ID分模块):server/src/core/0penCards.Core/Protocol/Client/-主协议定义-0x35000.Logic.Common.cs、0x35200.Logic.Fight.cs、0x35300.Logic.Role.cs0x39000.Logic.Arena.cs、0x37300.Logic.Mail.cs等,按功能模块拆分server/src/core/0penCards.Core/Protocol/Constants.cs -消息 ID 段常量定义server/src/core/0penCards.Core/Protocol/MsgAttribute.cs - [RequestMsg]/[ResponseMsg] 标记
-服务端内部 RPC协议:-server/src/server/0penCards.Server.Core/RPC/ -服务器间 RPC 消息(Account、Arena、Guild、Role等)
-共享数据结构:-server/src/core/0penCards.Core/Data/ -协议里用到的数据对象(0x22000.Role.cs、0x23800.Arena.cs 等)
-自动生成文件(不要手改)
-目录
-server/src/core/0penCards.GenCodec/generated_msg/
-server/src/server/0pencards.Server.GenORM/generated_msg/
-data/clientscript/Protocol/generated/
-内容
-C#序列化/反序列化 codec(gitignored)
-服务端 RPC codec (gitignored)
-Lua 版 codec,给客户端用
-整体架构
--协议在 C#的 OpenCards.Core.Protocol.Client 命名空间里手写定义,用[RequestMsg]标记配对OpenCards.GenCodec项目的 PostBuild 把这些定义生成对应的 C#codec和 Lua codec-客户端(Lua)用 data/ClientScript/Protocol/generated/下的 Lua 文件通信-服务端用C#codeC,消息ID按模块分段(Arena在0x39000段,等等)
-改协议的正确流程:移改 DpenCards.Core/ProtocoU/CLient/ 里的源文件  重新编译 DpenCards.TooLs (触发 PostBuild 生成新 codec)  前后端 codec 同步更新

+ 0 - 260
docs/多区配置指南.md

@@ -1,260 +0,0 @@
-# 服务器多区配置指南
-
-> 本文档说明如何在本项目架构基础上新增游戏区服(多区)。
-
----
-
-## 一、架构概览
-
-### 1.1 服务器节点类型
-
-| 节点名称 | 作用 | 部署数量 |
-|----------|------|----------|
-| **NameServer** | 全局命名/协调节点,管理所有ServiceNode | 全局1个 |
-| **PublicNode** | 公共服务节点(账号、公会、支付、好友、聊天、管理后台等) | 全局1个 |
-| **GameNode** | **游戏服节点**(每个区一个),包含Logic、Connector、Arena等 | 每区1个 |
-| **AccountNode** | 账号服务节点(可独立部署或在PublicNode中) | 1个 |
-
-### 1.2 关键概念
-
-- **区服 = GameNode**:一个 GameNode 对应一个游戏区。玩家在不同区之间数据完全隔离。
-- **serverID**:区的唯一数字标识,贯穿所有配置。
-- **Realm**:大区/领域概念,用于环境隔离(dev/qa/prod),与区服ID不同。
-- **Connector端口**:每个区的客户端连接端口不同(如19821、19822...),这是客户端区分不同区的入口。
-
----
-
-## 二、现有配置
-
-### 2.1 测试服当前只有一个区
-
-`start/_launch_server.xml` 中 GameNode1 配置:
-
-| 配置项 | 当前值 |
-|--------|--------|
-| serverID | 1 |
-| Connector端口 | 19821 |
-| Redis DB | db=1 |
-| RPC端口 | 17020 |
-
-### 2.2 已有参考:config_dev_2 是第二个区的完整配置
-
-项目已有 `config_dev_2` 目录,展示了**独立部署第二个区**的完整方案(不同服务器IP、独立端口)。
-
----
-
-## 三、开新区操作步骤
-
-在**同一台服务器**上增加新区(从1区扩展为2区),需要在 `_launch_server.xml` 中新增一个 `GameNode2` 配置。
-
-### 3.1 修改 `_launch_server.xml`
-
-在 `<ServiceNodes>` 内已有 `<GameNode1>` 之后,添加 `<GameNode2>`。
-
-以下是模板(参考 `start/_launch_server.xml` 中已有的 GameNode2 配置):
-
-```xml
-<!-- 游戏服节点 2区 -->
-<GameNode2>
-  <Redis>127.0.0.1,password=你的Redis密码,allowAdmin=true,syncTimeout=30000,responseTimeout=30000,connectRetry=1000,connectTimeout=10000;db=2</Redis>
-  <Mysql>server=127.0.0.1;User ID=root;Password=你的MySQL密码;database=orm</Mysql>
-  <Ip>127.0.0.1</Ip>
-  <RpcConfig>
-    <LocalNodeType>GameNode</LocalNodeType>
-    <LocalNodeName>GameNode2</LocalNodeName>
-    <LocalEndPoint>127.0.0.1:17021</LocalEndPoint>           <!-- RPC端口 +1 避免冲突 -->
-    <NameServerEndPoint>127.0.0.1:17000</NameServerEndPoint>
-    <RequestTickTimeMS>5000</RequestTickTimeMS>
-    <NetworkTimeoutMS>30000</NetworkTimeoutMS>
-    <DefaultTaskExecuteTimeout>60000</DefaultTaskExecuteTimeout>
-    <RpcCodec>OpenCards.Server.Core.Serializer</RpcCodec>
-    <AcceptTypeMappings>
-      <CenterService>OpenCards.Service.Center.CenterService</CenterService>
-      <AccountServer>OpenCards.Server.Account.AccountServer</AccountServer>
-      <ConnectorService>OpenCards.Server.Connector.ConnectorService</ConnectorService>
-      <ArenaManagerService>OpenCards.Server.Arena.ArenaManagerService</ArenaManagerService>
-      <ArenaValorService>OpenCards.Server.Arena.ArenaValorService</ArenaValorService>
-      <ArenaHighendService>OpenCards.Server.Arena.ArenaHighendService</ArenaHighendService>
-      <SessionService>OpenCards.Server.Connector.SessionService</SessionService>
-      <LogicService>OpenCards.Server.Logic.LogicService</LogicService>
-      <LogicManagerService>OpenCards.Server.Logic.LogicManagerService</LogicManagerService>
-    </AcceptTypeMappings>
-  </RpcConfig>
-  <StartService>
-    <!-- LogicManagerService -->
-    <LogicManagerService>
-      <ServiceName>LogicManagerService_2</ServiceName>       <!-- 名称改为 _2 -->
-      <ServiceType>LogicManagerService</ServiceType>
-      <Config>
-        <FightPostURL>http://127.0.0.1:8088/fight/reqstartbattle</FightPostURL>
-        <serverID>2</serverID>                                <!-- ⚠️ 区ID改为 2 -->
-      </Config>
-    </LogicManagerService>
-    <!-- Center -->
-    <CenterService>
-      <ServiceName>CenterService_2</ServiceName>              <!-- 名称改为 _2 -->
-      <ServiceType>CenterService</ServiceType>
-      <Config>
-        <serverID>2</serverID>                                <!-- ⚠️ 区ID改为 2 -->
-      </Config>
-    </CenterService>
-    <!-- ArenaManager -->
-    <ArenaManagerService>
-      <ServiceName>ArenaManagerService_2</ServiceName>        <!-- 名称改为 _2 -->
-      <ServiceType>ArenaManagerService</ServiceType>
-      <Config>
-        <ServerID>2</ServerID>                                <!-- ⚠️ 区ID改为 2 -->
-      </Config>
-    </ArenaManagerService>
-    <!-- ArenaHighend -->
-    <ArenaHighendService>
-      <ServiceName>ArenaHighendService_2</ServiceName>        <!-- 名称改为 _2 -->
-      <ServiceType>ArenaHighendService</ServiceType>
-      <Config></Config>
-    </ArenaHighendService>
-    <!-- Connector -->
-    <ConnectorService>
-      <ServiceName>ConnectorService_2</ServiceName>           <!-- 名称改为 _2 -->
-      <ServiceType>ConnectorService</ServiceType>
-      <Config>
-        <ServerId>2</ServerId>                                <!-- ⚠️ 区ID改为 2 -->
-        <Host>0.0.0.0</Host>
-        <Port>19822</Port>                                    <!-- ⚠️ 端口换一个,不能和1区冲突 -->
-        <NetCodec>OpenCards.Core.Serializer</NetCodec>
-        <KeepAlive>true</KeepAlive>
-        <KeepAliveInterval>30000</KeepAliveInterval>
-        <RecvBufferSize>16384</RecvBufferSize>
-        <SendBufferSize>16384</SendBufferSize>
-        <MaxConnections>300000</MaxConnections>
-      </Config>
-    </ConnectorService>
-  </StartService>
-</GameNode2>
-```
-
-### 3.2 关键配置变更清单
-
-新增一个区需要改以下值(**确保不与其他区冲突**):
-
-| 配置项 | 位置 | 1区值 | 2区值 | 说明 |
-|--------|------|-------|-------|------|
-| **Redis DB** | `GameNode/Redis` | `db=1` | `db=2` | 不同区用不同Redis DB隔离数据 |
-| **RPC端口** | `GameNode/RpcConfig/LocalEndPoint` | `17020` | `17021` | 节点间RPC通信端口 |
-| **Connector端口** | `ConnectorService/Config/Port` | `19821` | `19822` | **客户端连接端口**,每区唯一 |
-| **serverID** | `LogicManagerService`, `CenterService`, `ArenaManagerService`, `ConnectorService` | `1` | `2` | 区ID,必须全局唯一 |
-| **ServiceName** | 各Service的ServiceName | `xxx_1` | `xxx_2` | 服务实例名称后缀 |
-
-### 3.3 修改启动脚本
-
-在 `start/start.sh` 的 `cmd_start()` 中增加 GameNode2 的启动:
-
-```bash
-start_node "NameServer"
-start_node "PublicNode"
-start_node "GameNode1" "global.RealmID=1 global.ServerID=1"
-start_node "GameNode2" "global.RealmID=1 global.ServerID=2"    # 新增
-start_node "AccountNode1"
-```
-
-同时在 `cmd_stop()` 中增加停止(反序):
-
-```bash
-cmd_stop() {
-    stop_node "AccountNode1"
-    stop_node "GameNode2"      # 新增
-    stop_node "GameNode1"
-    stop_node "PublicNode"
-    stop_node "NameServer"
-    ...
-}
-```
-
-Windows 下则新建 `start/3_launch_game_r1s2.bat`:
-
-```bat
-title gameserver2
-dotnet ..\OpenCards.Server.DotNetCore.dll .\_launch_server.xml GameNode2 global.RealmID=1 global.ServerID=2
-```
-
-### 3.4 更新服务器列表
-
-客户端通过 `AccountServer` 从 OSS 下载 `serverlist.json` 来显示区服列表。需要在 OSS 上的 `serverlist.json` 中增加2区的条目:
-
-```json
-[
-  {
-    "id": 1,
-    "index": 0,
-    "name": "测试服1区",
-    "address": "47.109.111.123:19821",
-    "state": 1,
-    "is_open": true,
-    "capacity": 2000,
-    "serverid": 1,
-    "groupid": 1,
-    "note": "测试服"
-  },
-  {
-    "id": 2,
-    "index": 1,
-    "name": "测试服2区",
-    "address": "47.109.111.123:19822",
-    "state": 1,
-    "is_open": true,
-    "capacity": 2000,
-    "serverid": 2,
-    "groupid": 1,
-    "note": "测试服"
-  }
-]
-```
-
-> 注意 `address` 中的端口要对应 `ConnectorService` 的 `Port`(19822)。
-
-### 3.5 检查防火墙/安全组
-
-确保新增的端口(如 `19822`、`17021`)在防火墙和安全组中已放行。
-
----
-
-## 四、注意事项
-
-### 4.1 数据隔离
-
-- **Redis**:每个区使用不同的 `db` 编号(db=1, db=2, ...),或者在配置中使用独立的 Redis 实例。
-- **MySQL**:当前配置中 GameNode 共用同一个 MySQL `database=orm`,内部通过 `serverID` 字段区分数据。如果需要物理隔离,可以为每个区创建独立数据库。
-
-### 4.2 公共服务是共享的
-
-`PublicNode` 中的服务(公会GuildService、好友FriendService、支付PayServer等)是**跨区共享**的,不需要为每个区单独部署。它们内部通过 `serverID` 来区分不同区的数据。
-
-### 4.3 战斗服
-
-战斗服(`server_battle` 目录)是独立部署的,多区可以共用同一个战斗服,或每个区部署独立战斗服。
-
-### 4.4 GM命令与多区
-
-管理后台(`AdminService`)的GM命令需要指定 `server_list`(目标serverID)来精确定位操作哪个区。
-
-### 4.5 独立物理机部署多区
-
-如果要在**不同服务器**上部署新区(如 `config_dev_2` 的方案),需要:
-- 修改 `_launch_server.xml` 中的IP地址为各服务器的实际IP
-- NameServer 的地址需要在所有节点的 `NameServerEndPoint` 中保持一致
-- 确保各服务器间网络互通
-
----
-
-## 五、快速操作总结
-
-1. **复制 `GameNode1` 配置块** → 粘贴为 `GameNode2`
-2. **修改4个关键值**:serverID→2,Connector端口→19822,RPC端口→17021,Redis DB→2
-3. **添加启动脚本**:`start.sh` 增加 GameNode2 行 / Windows 新建 `.bat`
-4. **更新 serverlist.json**:添加2区信息到 OSS
-5. **放行端口**:防火墙开通 19822、17021
-6. **重启服务**:先停服,再按序启动
-
----
-
-> 文档生成时间:2026年6月11日
-> 基于当前 `start/_launch_server.xml` 和 `config_dev_2` 配置分析

+ 0 - 265
docs/文档/MongoDB4.0安装.md

@@ -1,265 +0,0 @@
-# MongoDB 4.0.28 Windows 安装指南
-
-> 安装日期:2026-06-08
-> 系统环境:Windows 10 x64
-> MongoDB 版本:4.0.28(4.0.x 系列最终版本)
-> 来源:[MongoDB Community Server](https://www.mongodb.com/try/download/community)
-> 安装方式:ZIP 便携版(解压即用)
-
----
-
-## 一、下载
-
-从 MongoDB 官方下载 MongoDB 4.0.28 Windows x64 ZIP 包:
-
-```
-https://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-4.0.28.zip
-```
-
-> 说明:MongoDB 4.0 已于 2022 年 4 月停止维护(EOL),
-> 官方 fastdl CDN 已不再提供 4.0.x 下载,上述旧版 archive 链接仍可用,
-> 文件大小约 250 MB。
-
-## 二、安装步骤
-
-### 1. 解压到目标目录
-
-将下载的 `mongodb-win32-x86_64-2008plus-ssl-4.0.28.zip` 解压,
-将其中的内容放到 `C:\MongoDB\`。
-
-解压后的目录结构:
-
-```
-C:\MongoDB\
-├── bin/                        # 可执行文件目录
-│   ├── mongod.exe              # 数据库服务端
-│   ├── mongo.exe               # 命令行客户端(mongo shell)
-│   ├── mongos.exe              # 分片路由服务
-│   ├── mongodump.exe           # 数据备份工具
-│   ├── mongorestore.exe        # 数据恢复工具
-│   ├── mongoexport.exe         # 数据导出工具
-│   ├── mongoimport.exe         # 数据导入工具
-│   ├── mongostat.exe           # 状态监控工具
-│   ├── mongotop.exe            # 性能监控工具
-│   ├── mongofiles.exe          # GridFS 文件管理
-│   ├── bsondump.exe            # BSON 转 JSON 工具
-│   ├── libeay32.dll            # OpenSSL 依赖库
-│   └── ssleay32.dll            # OpenSSL 依赖库
-├── data/                       # 数据存储目录(需手动创建)
-├── log/                        # 日志目录(需手动创建)
-├── LICENSE-Community.txt       # 许可证
-├── README                      # 说明文件
-└── THIRD-PARTY-NOTICES         # 第三方声明
-```
-
-### 2. 创建数据目录和日志目录
-
-```cmd
-mkdir C:\MongoDB\data
-mkdir C:\MongoDB\log
-```
-
-### 3. 验证安装
-
-```cmd
-cd C:\MongoDB\bin
-mongod.exe --version
-```
-
-输出示例:
-```
-db version v4.0.28
-git version: af1a9dc12adcfa83cc19571cb3faba26eeddac92
-allocator: tcmalloc
-modules: none
-build environment:
-    distmod: 2008plus-ssl
-    distarch: x86_64
-    target_arch: x86_64
-```
-
-## 三、启动 MongoDB
-
-### 方式一:命令行直接启动(前台运行)
-
-```cmd
-cd C:\MongoDB\bin
-mongod.exe --dbpath C:\MongoDB\data --logpath C:\MongoDB\log\mongod.log --logappend
-```
-
-> 启动成功后,该终端窗口会被占用。按 `Ctrl + C` 停止服务。
-
-### 方式二:后台启动(Windows)
-
-使用 PowerShell 或 CMD 以隐藏窗口方式启动:
-
-```cmd
-start /B mongod.exe --dbpath C:\MongoDB\data --logpath C:\MongoDB\log\mongod.log --logappend
-```
-
-### 方式三:安装为 Windows 服务(推荐,开机自启)
-
-**以管理员身份** 打开 CMD,执行:
-
-```cmd
-cd C:\MongoDB\bin
-mongod.exe --dbpath C:\MongoDB\data --logpath C:\MongoDB\log\mongod.log --logappend --install --serviceName MongoDB --serviceDisplayName "MongoDB 4.0"
-```
-
-启动服务:
-```cmd
-net start MongoDB
-```
-
-停止服务:
-```cmd
-net stop MongoDB
-```
-
-卸载服务:
-```cmd
-mongod.exe --remove --serviceName MongoDB
-```
-
-### 方式四:通过配置文件启动
-
-创建配置文件 `C:\MongoDB\mongod.cfg`:
-
-```yaml
-systemLog:
-  destination: file
-  path: C:\MongoDB\log\mongod.log
-  logAppend: true
-storage:
-  dbPath: C:\MongoDB\data
-net:
-  port: 27017
-  bindIp: 127.0.0.1
-```
-
-使用配置文件启动:
-```cmd
-mongod.exe --config C:\MongoDB\mongod.cfg
-```
-
-注册为服务(使用配置文件):
-```cmd
-mongod.exe --config C:\MongoDB\mongod.cfg --install --serviceName MongoDB
-```
-
-## 四、连接测试
-
-启动 MongoDB 后,新开一个终端窗口:
-
-```cmd
-cd C:\MongoDB\bin
-mongo.exe
-```
-
-在 mongo shell 中执行:
-
-```javascript
-// 查看数据库版本
-> db.version()
-4.0.28
-
-// 测试插入
-> db.test.insert({name: "Hello MongoDB", version: "4.0.28"})
-WriteResult({ "nInserted" : 1 })
-
-// 测试查询
-> db.test.find().pretty()
-{
-    "_id" : ObjectId("..."),
-    "name" : "Hello MongoDB",
-    "version" : "4.0.28"
-}
-
-// 查看所有数据库
-> show dbs
-
-// 查看当前数据库
-> db
-
-// 查看帮助
-> help
-```
-
-## 五、常用命令行参数
-
-| 参数 | 说明 | 示例 |
-|------|------|------|
-| `--dbpath` | 数据文件存储路径 | `--dbpath C:\MongoDB\data` |
-| `--logpath` | 日志文件路径 | `--logpath C:\MongoDB\log\mongod.log` |
-| `--logappend` | 日志追加模式(不覆盖) | `--logappend` |
-| `--port` | 监听端口 | `--port 27017` |
-| `--bind_ip` | 绑定 IP 地址 | `--bind_ip 127.0.0.1` |
-| `--auth` | 启用认证 | `--auth` |
-| `--install` | 安装为 Windows 服务 | `--install` |
-| `--remove` | 卸载 Windows 服务 | `--remove` |
-| `--serviceName` | 指定服务名称 | `--serviceName MongoDB` |
-| `--config` | 指定配置文件 | `--config C:\MongoDB\mongod.cfg` |
-
-## 六、配置环境变量(可选,方便全局使用)
-
-1. 右键"此电脑" → 属性 → 高级系统设置 → 环境变量
-2. 在"系统变量"中找到 `Path`,双击编辑
-3. 新建一条,填入 `C:\MongoDB\bin`
-4. 确定保存
-
-配置完成后,可在任意路径下直接运行 `mongo.exe` 和 `mongod.exe`。
-
-## 七、安全配置(建议)
-
-### 启用认证
-
-1. 先以无认证模式启动并创建管理员用户:
-
-```cmd
-mongod.exe --dbpath C:\MongoDB\data --logpath C:\MongoDB\log\mongod.log --logappend
-```
-
-```javascript
-// 连接后创建管理员
-> use admin
-> db.createUser({
-    user: "admin",
-    pwd: "your_password",
-    roles: ["root"]
-})
-```
-
-2. 重启 MongoDB 并启用认证:
-
-```cmd
-mongod.exe --dbpath C:\MongoDB\data --logpath C:\MongoDB\log\mongod.log --logappend --auth
-```
-
-3. 使用认证方式连接:
-
-```cmd
-mongo.exe -u admin -p your_password --authenticationDatabase admin
-```
-
-## 八、注意事项
-
-1. **仅支持 64 位系统**:Windows 7/10/11 x64 或 Windows Server 2008 R2+ x64
-2. **MongoDB 4.0 已 EOL**:官方已于 2022 年 4 月停止对该版本的支持,不建议用于生产环境
-3. **路径避免中文和空格**:安装路径不要包含中文和空格
-4. **端口占用**:如果 27017 端口被占用,使用 `--port` 参数指定其他端口
-5. **数据备份**:定期使用 `mongodump` 备份数据库
-6. **Windows 防火墙**:如需远程访问,请在防火墙中开放 MongoDB 端口(27017)
-
-## 九、卸载
-
-1. 如果安装了 Windows 服务,先以管理员身份执行:
-   ```cmd
-   net stop MongoDB
-   mongod.exe --remove --serviceName MongoDB
-   ```
-2. 删除 `C:\MongoDB` 目录即可
-
----
-
-> 项目主页:https://www.mongodb.com/
-> 官方文档:https://www.mongodb.com/docs/v4.0/

+ 0 - 284
docs/文档/OpenCards.Server.Main启动问题排查记录.md

@@ -1,284 +0,0 @@
-# OpenCards.Server.Main 启动问题排查记录
-
-> 整理自 Cursor Agent 对话(2026-06-10)  
-> 项目路径:`D:\WorkSpace\project\chuanzhanServer`
-
----
-
-## 目录
-
-1. [Quartz 启动报错:System.Configuration.ConfigurationManager 找不到](#1-quartz-启动报错systemconfigurationconfigurationmanager-找不到)
-2. [LuaTemplateLoader 初始化 NullReferenceException](#2-luatemplateloader-初始化-nullreferenceexception)
-3. [TableManager.LoadTemplates 报错且 _IdMap 为 null](#3-tablemanagerloadtemplates-报错且-_idmap-为-null)
-4. [activity_task.lua 存在但仍报错的误解](#4-activity_tasklua-存在但仍报错的误解)
-5. [推荐启动配置清单](#5-推荐启动配置清单)
-
----
-
-## 1. Quartz 启动报错:System.Configuration.ConfigurationManager 找不到
-
-### 现象
-
-启动 `OpenCards.Server.Main` 时出现类似日志:
-
-```
-2026-06-10 10:14:45,047 INFO  CenterService@NODE_NAME - Service Starting...
-2026-06-10 10:14:45,107 ERROR Quartz - One or more errors occurred. (Could not load file or assembly 'System.Configuration.ConfigurationManager, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. 系统找不到指定的文件。)
-System.AggregateException : One or more errors occurred. (Could not load file or assembly 'System.Configuration.ConfigurationManager, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. 系统找不到指定的文件。)
-```
-
-### 结论
-
-**不是** `_launch_server_*.xml` 等路径配置文件的问题,而是 **.NET 依赖 DLL 版本不匹配**。
-
-### 原因分析
-
-调用链:
-
-1. `CenterService` 启动时调用 `Provider.CreateCornJobAsync()` 注册定时任务
-2. `DeepFrozen.RPC.dll` 内部使用 **Quartz** 调度器
-3. `Quartz.dll`(3.3.2.0)编译时依赖 `System.Configuration.ConfigurationManager` **版本 4.0.3.0**
-
-| 组件 | 需要的版本 | 输出目录原有版本 |
-|------|-----------|----------------|
-| Quartz.dll | ConfigurationManager **4.0.3.0** | **4.0.1.0** |
-
-`OpenCards.Server.DotNetCore` 项目已引用该 NuGet 包,但 `OpenCards.Server.Main` 未引用,构建时不会把正确版本复制到 `_output.server`。
-
-### 修复方案
-
-在 `OpenCards.Server.Main.csproj` 中添加:
-
-```xml
-<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.7.0" />
-```
-
-重新 `dotnet build` 后,输出目录中 DLL 版本变为 **4.0.3.0**,与 Quartz 要求一致。
-
-### 操作步骤
-
-```powershell
-cd D:\WorkSpace\project\chuanzhanServer\server\src\server\OpenCards.Server.Main
-dotnet build -c Debug
-cd ..\..\_output.server
-.\OpenCards.Server.Main.exe local
-```
-
----
-
-## 2. LuaTemplateLoader 初始化 NullReferenceException
-
-### 现象
-
-`Program.cs` 第 129 行:
-
-```csharp
-new DeepCore.Lua.LuaTemplateLoader(true, ad);
-```
-
-调试时报 `System.NullReferenceException`,堆栈类似:
-
-```
-Error initializing UnityScriptLoader : System.NullReferenceException
-   at MoonSharp.Interpreter.Compatibility.Frameworks.FrameworkClrBase.GetProperty(Type type, String name)
-   at MoonSharp.Interpreter.Loaders.UnityAssetsScriptLoader.LoadResourcesWithReflection(String assetsPath)
-```
-
-已知运行时 `ad` 的实例为 `{DeepCore.Template.MoonSharp.MoonSharpLuaAdapter}`。
-
-### 结论
-
-**不是 `ad` 为空**,而是构造函数第一个参数 `true` 触发了 Unity 专用脚本加载器初始化。
-
-### 原因分析
-
-- 构造函数签名:`LuaTemplateLoader(Boolean instance, ILuaAdapter adapter)`
-- `instance = true`:按 **Unity 客户端** 模式初始化
-- 内部创建 `UnityAssetsScriptLoader`,调用 `LoadResourcesWithReflection()`
-- 该方法通过反射加载 `UnityEngine.Resources`、`UnityEngine.TextAsset`
-- 服务端 .NET 控制台环境**没有** `UnityEngine.dll`,`Type.GetType(...)` 返回 null
-- `FrameworkClrBase.GetProperty(null, "name")` → NullReferenceException
-
-该异常在 MoonSharp 内部被 `catch` 并打印日志,**一般不会向外抛出**,程序可继续运行。若 Visual Studio 开启「引发时中断」,调试器会在 `Program.cs` 该行停下。
-
-### 重要:true 与 false 的区别
-
-| 参数 | `LuaTemplateLoader.Instance` | 对 TableManager 的影响 |
-|------|------------------------------|------------------------|
-| `true` | **有值** | `LoadTemplates` 正常工作 |
-| `false` | **null** | `LoadTemplates` 必然 NRE |
-
-**不能**为避免 Unity 日志而将 `true` 改为 `false`,否则所有配置表加载失败(见第 3 节)。
-
-### 处理建议
-
-- 服务端保持 `new DeepCore.Lua.LuaTemplateLoader(true, ad);`
-- 忽略控制台中的 `Error initializing UnityScriptLoader` 日志
-- 或在 VS 中关闭对该异常的「引发时中断」
-
----
-
-## 3. TableManager.LoadTemplates 报错且 _IdMap 为 null
-
-### 现象
-
-`TableManager.cs` 中:
-
-```csharp
-_IdMap = loader.LoadTemplates<int, Table_ActivityTask>(nameof(Table_ActivityTask.Id), "activity_task");
-```
-
-该行报错,且 `_IdMap` 值为 `null`。
-
-### 根因
-
-若 `Program.cs` 使用了 `new LuaTemplateLoader(false, ad)`:
-
-1. `LuaTemplateLoader.Instance` 不会被赋值(保持 null)
-2. `loader.LoadTemplates(...)` 内部依赖该静态单例
-3. 在 `DeepCore.TemplateLoader.XLSLoader.LoadTemplates` 处抛出 `NullReferenceException`
-4. `_IdMap` 赋值失败,保持 null
-
-### 修复方案
-
-恢复 `Program.cs` 中的正确写法:
-
-```csharp
-DeepCore.GameEvent.Lua.LuaEventManager.DefaultAdapter = new DeepCore.Template.MoonSharp.MoonSharpLuaAdapter();
-var ad = DeepCore.GameEvent.Lua.LuaEventManager.DefaultAdapter;
-if (ad != null)
-{
-    // 必须传 true,否则 LuaTemplateLoader.Instance 不会被赋值,TableManager.LoadTemplates 会 NRE。
-    // true 时 MoonSharp 会尝试初始化 Unity 脚本加载器并打印 Error initializing UnityScriptLoader,可忽略。
-    new DeepCore.Lua.LuaTemplateLoader(true, ad);
-    CardsServerTemplateManager.TemplateDataRootPath =
-        CardsServerTemplateManager.ResolveServerDataRoot();
-    new DeepCore.Lua.LuaDataCenter(ad,
-        Path.Combine(CardsServerTemplateManager.TemplateDataRootPath, "templates_lua"));
-}
-CardsServerTemplateManager.Instance.Init();
-```
-
-### 初始化顺序(不可打乱)
-
-1. `MoonSharpLuaAdapter` → `LuaEventManager.DefaultAdapter`
-2. `new LuaTemplateLoader(true, ad)`
-3. `new LuaDataCenter(ad, templates_lua 路径)`
-4. `CardsServerTemplateManager.Instance.Init()` → 内部 `TableManager.LoadAllConfig`
-
-### 验证
-
-```csharp
-Table_ActivityTaskManager.IdMap.Count  // 正常应为 3
-Table_ActivityTaskManager.GetById(1)   // 应有数据
-```
-
----
-
-## 4. activity_task.lua 存在但仍报错的误解
-
-### 用户疑问
-
-`ClientScript\Data\activity_new.xlsx\activity_task.lua` 文件存在,为何仍报错?是否所有 `.lua` 都没加载?
-
-### 关键说明
-
-#### 服务端不读 ClientScript 目录
-
-服务端模板根目录解析为:
-
-```
-server/src/data/ServerData/
-```
-
-`TableManager` 实际读取:
-
-```
-data/ServerData/templates_lua/activity_new.xlsx/activity_task.lua
-```
-
-**不会**读取 `data/ClientScript/Data/activity_new.xlsx/activity_task.lua`。  
-`ClientScript` 供 Unity 客户端使用;`ServerData/templates_lua` 供服务端使用,两套目录并行维护。
-
-#### activity_task.lua 可以正常加载
-
-在 `LuaTemplateLoader(true, ad)` 条件下,日志可见:
-
-```
-WARN LuaTemplateLoader - Field not found ... Table_ActivityTask.StageName : Sheet=activity_task
-WARN LuaTemplateLoader - Field not found ... Table_ActivityTask.Desc : Sheet=activity_task
-```
-
-说明 Lua 已解析,只是 `StageName`、`Desc` 在服务端 C# 类中未定义(客户端展示字段),属于正常 WARN。
-
-服务端 `Table_ActivityTask` 仅包含:
-
-```csharp
-public int Id;
-public int Type;
-public int Target;
-public int DropGroupId;
-```
-
-#### 并非所有 lua 未加载
-
-启动日志中有大量:
-
-```
-TemplateDataCenter - Reload : CacheData : File=templates_lua/activity_new.xlsx
-```
-
-表示对应 xlsx 目录下的 lua 已成功缓存。真正的加载失败会由 `LogicUtils.LogError` 记录并 `throw`。
-
-### 日志类型对照
-
-| 日志 | 含义 |
-|------|------|
-| `Error initializing UnityScriptLoader` | Unity 加载器初始化失败,不影响磁盘读 Lua |
-| `Field not found ... StageName/Desc` | Lua 已加载,客户端字段被忽略 |
-| `TemplateDataCenter - Reload` | 表加载成功 |
-| `LogicUtils.LogError` + 异常堆栈 | 真正的表加载失败 |
-
----
-
-## 5. 推荐启动配置清单
-
-### 已修复项
-
-- [x] `OpenCards.Server.Main.csproj` 添加 `System.Configuration.ConfigurationManager` 4.7.0
-- [x] `Program.cs` 使用 `LuaTemplateLoader(true, ad)` 而非 `false`
-
-### 启动前检查
-
-1. 工作目录为 `_output.server`(或确保 `ResolveServerDataRoot` 能找到 `map/map_events.json`)
-2. Redis 已启动(配置见 `_launch_server_local.xml`)
-3. 模板数据存在于 `server/src/data/ServerData/templates_lua/`
-
-### 可忽略的启动日志
-
-- `Error initializing UnityScriptLoader : NullReferenceException`
-- `Field not found in C# class ... StageName / Desc / Name` 等客户端字段警告
-
-### 需要关注的错误
-
-- `System.Configuration.ConfigurationManager` 程序集找不到(需重新 build)
-- `TableManager` 中 `LoadTemplates` 的 NRE(检查是否误用 `LuaTemplateLoader(false, ad)`)
-- `Cannot find ServerData directory`(工作目录或数据路径错误)
-
----
-
-## 附录:相关文件路径
-
-| 文件 | 说明 |
-|------|------|
-| `server/src/server/OpenCards.Server.Main/Program.cs` | 主程序入口,Lua 初始化 |
-| `server/src/server/OpenCards.Server.Main/OpenCards.Server.Main.csproj` | Main 项目,Quartz 依赖修复 |
-| `server/src/server/OpenCards.Server.Core/TableManager.cs` | 配置表加载 |
-| `server/src/server/OpenCards.Server.Core/Table/Table_ActivityTask.cs` | activity_task 表定义 |
-| `server/src/data/ServerData/templates_lua/activity_new.xlsx/activity_task.lua` | 服务端实际读取的 lua |
-| `server/src/data/ClientScript/Data/activity_new.xlsx/activity_task.lua` | 客户端 lua(服务端不读) |
-| `server/src/_output.server/` | 编译输出与运行目录 |
-
----
-
-*文档生成时间:2026-06-10*

+ 0 - 159
docs/文档/Redis7.0安装.md

@@ -1,159 +0,0 @@
-# Redis 7.0.15 Windows 安装指南
-
-> 安装日期:2026-06-08
-> 系统环境:Windows 10 x64
-> Redis 版本:7.0.15
-> 来源:[redis-windows/redis-windows](https://github.com/redis-windows/redis-windows)
-
----
-
-## 一、下载
-
-从 GitHub 社区维护版下载 Redis 7.0.15(msys2 编译,含服务安装支持):
-
-```
-https://github.com/redis-windows/redis-windows/releases/download/7.0.15/Redis-7.0.15-Windows-x64-msys2-with-Service.zip
-```
-
-## 二、安装步骤
-
-### 1. 解压到目标目录
-
-将下载的 `Redis-7.0.15-Windows-x64-msys2-with-Service.zip` 解压到 `C:\Redis\`
-
-解压后的文件清单:
-
-| 文件 | 说明 |
-|------|------|
-| `redis-server.exe` | Redis 服务端主程序 |
-| `redis-cli.exe` | Redis 命令行客户端 |
-| `redis-benchmark.exe` | 性能测试工具 |
-| `redis-check-aof.exe` | AOF 文件检查/修复工具 |
-| `redis-check-rdb.exe` | RDB 文件检查/修复工具 |
-| `redis-sentinel.exe` | 哨兵模式(高可用) |
-| `RedisService.exe` | Windows 服务包装程序 |
-| `redis.conf` | 服务端配置文件 |
-| `sentinel.conf` | 哨兵配置文件 |
-| `msys-2.0.dll` | 运行时依赖库 |
-| `msys-crypto-3.dll` | 加密依赖库 |
-| `msys-ssl-3.dll` | SSL 依赖库 |
-| `install_redis_service.bat` | 一键安装服务脚本 |
-| `uninstall_redis_service.bat` | 一键卸载服务脚本 |
-| `start.bat` | 一键启动脚本 |
-
-### 2. 验证安装
-
-打开 CMD 或 PowerShell,执行:
-
-```cmd
-cd C:\Redis
-redis-server.exe --version
-```
-
-输出示例:
-```
-Redis server v=7.0.15 sha=00000000:0 malloc=libc bits=64 build=6ea37fe00848aa50
-```
-
-## 三、启动 Redis
-
-### 方式一:命令行直接启动(前台运行)
-
-```cmd
-cd C:\Redis
-redis-server.exe redis.conf
-```
-
-按 `Ctrl + C` 停止服务。
-
-### 方式二:双击 start.bat 启动
-
-直接双击 `C:\Redis\start.bat`,一键启动。
-
-### 方式三:安装为 Windows 服务(开机自启)
-
-**以管理员身份** 打开 CMD,执行:
-
-```cmd
-sc.exe create Redis binpath=C:\Redis\RedisService.exe start= auto
-net start Redis
-```
-
-停止服务:
-```cmd
-net stop Redis
-```
-
-卸载服务:
-```cmd
-sc.exe delete Redis
-```
-
-> 也可以直接双击 `install_redis_service.bat`(需管理员权限)一键安装服务。
-
-## 四、连接测试
-
-启动 Redis 后,新开一个终端窗口:
-
-```cmd
-cd C:\Redis
-redis-cli.exe
-```
-
-在客户端中执行:
-```
-127.0.0.1:6379> ping
-PONG
-127.0.0.1:6379> set test "Hello Redis"
-OK
-127.0.0.1:6379> get test
-"Hello Redis"
-127.0.0.1:6379> info server
-# Server
-redis_version:7.0.15
-...
-```
-
-## 五、常用配置(redis.conf)
-
-| 配置项 | 说明 | 默认值 |
-|--------|------|--------|
-| `bind 127.0.0.1` | 绑定 IP(远程访问改为 `0.0.0.0`) | `127.0.0.1` |
-| `port 6379` | 监听端口 | `6379` |
-| `requirepass` | 认证密码(建议设置) | 注释掉(无密码) |
-| `maxmemory` | 最大内存限制 | 注释掉(无限制) |
-| `databases 16` | 数据库数量 | `16` |
-| `logfile` | 日志文件路径 | 注释掉(输出到 stdout) |
-| `dir ./` | 数据文件存储目录 | `./` |
-| `dbfilename dump.rdb` | RDB 持久化文件名 | `dump.rdb` |
-| `appendonly no` | 是否开启 AOF 持久化 | `no` |
-
-## 六、配置环境变量(可选,方便全局使用)
-
-1. 右键"此电脑" → 属性 → 高级系统设置 → 环境变量
-2. 在"系统变量"中找到 `Path`,双击编辑
-3. 新建一条,填入 `C:\Redis`
-4. 确定保存
-
-配置完成后,可在任意路径下直接运行 `redis-cli.exe`。
-
-## 七、注意事项
-
-1. **仅支持 64 位系统**:Windows 7/10/11 x64 或 Windows Server x64
-2. **仅推荐开发/测试环境使用**:Redis 官方建议生产环境部署在 Linux 上
-3. **路径避免中文和空格**:安装路径不要包含中文和空格
-4. **端口占用**:如果 6379 端口被占用,修改 `redis.conf` 中的 `port` 值
-5. **内存限制**:建议在 `redis.conf` 中配置 `maxmemory`,防止内存无限增长
-
-## 八、卸载
-
-1. 如果安装了 Windows 服务,先以管理员身份执行:
-   ```cmd
-   net stop Redis
-   sc.exe delete Redis
-   ```
-2. 删除 `C:\Redis` 目录即可
-
----
-
-> 项目主页:https://github.com/redis-windows/redis-windows

+ 0 - 159
docs/文档/Workspace.sln 编译问题总结.md

@@ -1,159 +0,0 @@
-# Workspace.sln 编译问题总结
-
-> 问题日期:2026-06-08
-
----
-
-## 错误现象一
-
-使用 Visual Studio 2022 编译 `Workspace.sln` 时,`OpenCards.Tools` 项目报错:
-
-```
-MSB3073: 命令"dotnet D:\WorkSpace\chuanzhanserver\server\src\core\OpenCards.Tools\bin\Debug\net5.0\OpenCards.Tools.dll
-call copylua"已退出,代码为 4。
-```
-
-错误位置:`src\core\OpenCards.Tools\OpenCards.Tools.csproj` 第 26 行
-
----
-
-## 错误触发点
-
-`OpenCards.Tools.csproj` 中的 PostBuild 事件:
-
-```xml
-<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="'$(OS)' == 'Windows_NT'">
-  <Exec Command="dotnet $(TargetPath)&#xD;&#xA;call copylua" />
-</Target>
-```
-
-该事件在编译成功后依次执行两步:
-
-| 步骤 | 命令 | 作用 |
-|------|------|------|
-| 1 | `dotnet $(TargetPath)` | 运行编译产物 OpenCards.Tools.dll(代码生成工具) |
-| 2 | `call copylua` | 调用 `copylua.bat`,复制生成的 Lua 协议文件到 data 目录 |
-
-任意一步失败(退出码 ≠ 0)都会导致 MSB3073 错误。
-
----
-
-## 根因分析
-
-### 可能原因 1:.NET 5.0 SDK 未安装
-
-| 项 | 编译前 | 编译后 |
-|----|--------|--------|
-| .NET SDK | 仅 8.0.101 | 5.0.408 + 8.0.101 |
-| .NET 5.0 Runtime | 5.0.17 ✅ | 5.0.17 ✅ |
-| .NET 5.0 Targeting Pack | ❌ **缺失** | ✅ |
-
-`OpenCards.Tools.csproj` 指定了 `<TargetFramework>net5.0</TargetFramework>`,但系统仅安装了 .NET 8.0 SDK,缺少 .NET 5.0 的 targeting pack,导致 MSBuild 无法编译 `net5.0` 项目。
-
-即使 .NET 5.0 运行时存在(用于运行已有应用),也**不能替代 SDK 中的 targeting pack**来进行编译。
-
-### 可能原因 2:`copylua.bat` 依赖的 data 目录缺失
-
-`copylua.bat` 内执行了多个 `xcopy` 命令,将生成的 Lua 文件复制到目标位置,但以下源目录不存在:
-
-| 目录 | 状态 |
-|------|------|
-| `data/ClientScript/Protocol/generated/` | ❌ 不存在 |
-| `data/ServerData/templates_lua_area/cn/` | ❌ 不存在 |
-| `data/ServerData/templates_lua/` | ❌ 不存在 |
-| `src/core/OpenCards.GenCodec/generated_msg_lua/` | ✅ 存在 |
-
-`xcopy` 从不存在目录复制文件时会报错退出,导致 `copylua.bat` 失败。`data/` 目录通常来自独立的资源仓库或部署包,需单独获取。
-
-### 根本原因: `copylua.bat` 内、路径层级问题。
-
-D:\WorkSpace\chuanzhanserver\server\src\core下边,OpenCards.Tools文件夹和OpenCards.TestAll文件夹种都有 `copylua.bat`脚本,并且脚本内容的set和copy都使用了错误的路径标识,导致后续编译也产生了相应错误。
-
----
-
-## 解决措施
-
-### 措施 1:安装 .NET 5.0 SDK ✅ 已完成
-
-使用 `dotnet-install.ps1` 脚本安装:
-
-```powershell
-powershell -ExecutionPolicy Bypass -File dotnet-install.ps1 -Channel 5.0 -Version 5.0.408 -InstallDir "C:\Program Files\dotnet"
-```
-
-安装后 SDK 列表:
-
-```
-5.0.408 [C:\Program Files\dotnet\sdk]
-8.0.101 [C:\Program Files\dotnet\sdk]
-```
-
-### 措施 2:data 目录缺失 ⚠️ 待处理
-
-`data/` 目录下的 `ClientScript` 和 `ServerData` 为项目运行时资源,需确认来源:
-
-- 是否来自独立的资源仓库(Git submodule / 独立 repo)
-- 是否为构建过程的中间产物(由其他工具生成)
-- 是否可跳过该步骤(从 PostBuild 中移除或添加条件跳过)
-
-### 措施 3: 修改copylua.bat
-
-D:\WorkSpace\chuanzhanserver\server\src\core下边,OpenCards.Tools文件夹和OpenCards.TestAll文件夹种都有 `copylua.bat`脚本, `copylua.bat` 脚本内容的set和copy都使用了错误的路径标识。
-
-\server\src\core\OpenCards.Tools文件夹里边:
-
-```shell
-需要将copylua.bat文件第3行修改为:
-@SET TargetDirLua=%SolutionDir%..\..\data\ClientScript\Protocol\generated\
-第9行修改为:
-@SET LuaAreaDirPath=%SolutionDir%..\..\data\ServerData\templates_lua_area\cn
-第10行修改为:
-@SET LuaDirPath=%SolutionDir%..\..\data\ServerData\templates_lua\
-```
-
-\server\src\server\OpenCards.TestAll文件夹里边:
-
-```shell
-第2、3行分为改为:
-@SET LuaAreaDirPath=%SolutionDir%..\..\data\ServerData\templates_lua_area\cn
-@SET LuaDirPath=%SolutionDir%..\..\data\ServerData\templates_lua\
-```
-
-修改之后重新编译即可。
-
----
-
-
-
-## 错误现象二
-
-以上问题解决后继续编译,出现新的错误。
-
-```c#
-CS0246类型或命名空间名"ArenaHighendDataMapping" (是否缺少 using指令或程序集引用?)
-CS0246类型或命名空间名“ArenaHighendRankDataMapping" (是否缺少 using 指令或程序集引用?)
-```
-
-观察发现,报错的方法基本上都是以DataMapping为后缀,猜想是否协议文件或者导表问题导致的。
-
-## 原因分析
-
-#### 分析一:未导表/编译
-
-可能是没有进行导表/协议编译操作。
-
-⏺ OpenCards.Server.GenORM/.gitignore 把整个 generated_orm/ 目录排除在 git 之外。这些映射类(ArenaHighendDataMapping、AccountCountDataMapping 等)都是由OpenCards.Server.Tools 在编译后自动生成的,不在仓库里。
-
-编译链路是:
-  1. 编译 OpenCards.Server.Tools → PostBuild 运行代码生成工具 → 生成 generated_orm/*.cs
-  2. 编译 OpenCards.Server.GenORM → 用那些生成的文件
-  3. 编译 ArenaHighendService.cs、AccountServer.cs → 使用 GenORM 里的类型
-
-#### 分析二:执行顺序
-
-报错的原因: 如果是新拉的代码或 generated_orm/ 被清空,直接编译整个解决方案时,GenORM 会先于 Server.Tools 编译,此时生成文件还不存在,导致所有 Mapping类型都找不到。
-
-## 解决方案:
-
-清理解决方案后重新生成。查看是否有对应文件,如果没有:在 Visual Studio 中,右键 OpenCards.Server.Tools → 单独生成(Build) 这一个项目,让 PostBuild 生成 generated_orm/*.cs 文件,之后再重新生成整个解决方案即可。
-

+ 0 - 72
docs/文档/协议修改.md

@@ -1,72 +0,0 @@
-# 客户端&服务器协议
-
-> 问题日期:2026-06-08
-
-
-
-### 协议定义(手写源文件)
-
-客户端&服务端协议(按消息ID分模块):
-
-```
--server/src/core/openCards.Core/Protocol/Client/一主协议定义
--0x35000.Logic.Common.cs、0x35200.Logic.Fight.cs、0x35300.Logic.Role.cs...
--0x39000.Logic.Arena.cs、0x37300.Logic.Mail.cs 等,按功能模块拆分
--server/src/core/OpenCards.Core/Protocol/Constants.cs -消息ID 段常量定义
--server/src/core/0penCards.Core/Protocol/MsgAttribute.cs -[RequestMsg]/[ResponseMsg]标记
-```
-
-服务端内部RPC协议:
-
-```
-一 server/src/server/0penCards.Server.Core/RPC/一 服务器间 RPC 消息 (Account、Arena、Guild、Role 等)
-```
-
-共享数据结构:
-
-```
-一 server/src/core/0penCards.Core/Data/一协议里用到的数据对象(0x22000.Role.cs、0x23800.Arena.cs 等)
-```
-
-自动生成文件 (不要手改)
-
-| 目录                                                     | 内容                               |
-| -------------------------------------------------------- | ---------------------------------- |
-| server/src/core/0penCards.GenCodec/generated_msg/        | C#序列化/反序列化codec(gitignored) |
-| server/src/server/0penCards.Server.GenoRM/generated_msg/ | 服务端 RPC codec(gitignored)      |
-| data/ClientScript/Protocol/generated/                    | Lua 版 codec,给客户端用           |
-
-
-
-整体架构
-
-- 协议在C#的OpenCards.Core.Protocol.Client命名空间里手写定义,用[RequestMsg]标记配对
-
-- OpenCards.GenCodec项目的PostBuild把这些定义生成对应的C#codec和Luacodec
-
-- 客户端用 data/ClientScript/Protocol/generated/ 下的Lua文件通信
-
-- 服务端用 C# codec,消息 ID 按模块分段 (Arena在0X39000段 等等)
-
-  **改协议的正确流程:修改OpenCards.Core/Protocol/Client/里的源文件→重新编译OpenCards.Tools(触发PostBuild生成新codec)→前后端codec同步更新。**
-
-
-
-## 新增协议
-
-**协议源文件地址:**
-
-D:\WorkSpace\project\chuanzhanServer\server\src\core\OpenCards.Core\
-
-**协议文件具体书写规范请参考: 协议.md**
-
-在/Data中新增协议
-
-在/ORM中定义结构体
-
-在Protocol中,定义协议请求
-
-协议定义完成后,需要打开visual studio,生成解决方案
-
-生成之后的.lua文件在../OpenCards.GenCodec中可以找到;生成之后发给客户端查看,没有问题再打包;打包完成后发布到测试服进行测试。
-

+ 0 - 77
docs/文档/打包发布起服.md

@@ -1,77 +0,0 @@
-# 打包&起服流程
-
-> 问题日期:2026-06-10
-
-服务器linux地址:
-
-43.226.57.217
-用户名:root
-密码:VOGevp67
-端口:22
-
-
-
-### 打包
-
-server.output文件打包,有对应命令:
-
-D:\WorkSpace\project\chuanzhanServer\server\src\server\OpenCards.Server.DotNetCore\server打包.bat
-
-```bash
-dotnet publish OpenCards.Server.DotNetCore.csproj -c Release -r linux-x64 --self-contained false -o ./_output.server
-pause
-```
-
-
-
-战斗服server_battle.output,在
-D:\WorkSpace\project\chuanzhanServer\server_battle\打包.bat中
-
-```bash
-@echo off
-set MSBUILD="%ProgramFiles%\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\MSBuild.exe"
-if not exist %MSBUILD% set MSBUILD="%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe"
-%MSBUILD% server_battle.sln /t:Rebuild /p:Configuration=Release /v:m
-if errorlevel 1 pause & exit /b 1
-echo.
-echo 打包完成: %~dp0_output.battle.server
-pause
-```
-
-
-
-
-
-### 发布并重启服务器
-
-接入linux之后,进入地址
-
-```shell
-cd /data/CzServer/develop
-```
-
-
-
-将旧的zip包删除,以上步骤生成的新zip放入进来,继续再执行:
-
-```shell
-#注意_output.server.zip 和 _output.server文件夹都需要删除
-
-#替换/删除完成后,进入对应目录
-cd /data/CzServer/_output.server/dev 
-# 执行重启命令
-./start.sh restart
-```
-
-
-
-战斗服:使用以下命令重启即可
-
-```shell
-#注意,战斗服的旧文件也需要删除
-#战斗服的文件存放路径不一样;同样完成操作后重启
-cd /data/CzServer
-./publish.sh battle
-进入文件夹后,有 start.sh* 和stop.sh* 2个脚本文件,分别代表起服和停服
-```
-

+ 0 - 447
docs/文档/服务器起服&连接异常.md

@@ -1,447 +0,0 @@
-# 服务器起服 & 客户端连接异常排查
-
-> 整理自 Cursor Agent 对话(2026-06-11)  
-> 服务端项目:`D:\WorkSpace\project\chuanzhanServer`  
-> Unity 客户端:`D:\WorkSpace\jyyz_game\client`
-
----
-
-## 目录
-
-1. [架构与端口规划](#1-架构与端口规划)
-2. [本地起服完整流程](#2-本地起服完整流程)
-3. [起服异常:AccountServer 拉取静态配置失败](#3-起服异常accountserver-拉取静态配置失败)
-4. [Unity 客户端连接服务器流程](#4-unity-客户端连接服务器流程)
-5. [`address=` 日志在哪里打印](#5-address-日志在哪里打印)
-6. [为什么本地 `serverlist.json` 没生效](#6-为什么本地-serverlistjson-没生效)
-7. [验证清单](#7-验证清单)
-8. [常见误区](#8-常见误区)
-9. [关键文件索引](#9-关键文件索引)
-10. [总结](#10-总结)
-
----
-
-## 1. 架构与端口规划
-
-### 1.1 两类地址(极易混淆)
-
-配置和日志里的 `127.0.0.1` 分两类:
-
-| 类型 | 说明 | 谁提供 |
-|------|------|--------|
-| **静态配置 URL** | `serverlist.json`、停服配置等 | 需独立 HTTP 静态服务(80 / 8000) |
-| **游戏服自身服务** | 账号、支付、Connector TCP 等 | `OpenCards.Server.Main` 进程 |
-
-### 1.2 端口对照表
-
-| 服务 | 端口 | 协议 | 说明 |
-|------|------|------|------|
-| 静态配置(无端口) | 80 | HTTP | 停服/更新/版本记录 JSON |
-| 区服列表 | 8000 | HTTP | `serverlist.json` |
-| AccountServer | 18081 | HTTP | 登录、账号 API |
-| PayServer | 18082 | HTTP | 支付 |
-| AdminService | 18088 | HTTP | GM 后台 |
-| **Connector(客户端游戏连接)** | **19821** | **TCP** | `local` 单节点默认 |
-| Connector 2 区 | 19822 | TCP | `local2` 多节点 2 区 |
-| Redis | 6379 | TCP | 依赖服务 |
-| MySQL | 3306 | TCP | Pay 等模块依赖 |
-
-> **注意**:`18081` 是 HTTP 账号接口,`19821` 才是 Unity 客户端建立游戏长连接的 TCP 端口。`serverlist.json` 里的 `address` 必须写 Connector 地址(如 `127.0.0.1:19821`),不能写 `18081`。
-
----
-
-## 2. 本地起服完整流程
-
-建议每次本地调试按以下顺序操作:
-
-### 第一步:启动依赖
-
-- Redis(`6379`)
-- MySQL(若启用 Pay 等模块)
-
-### 第二步:启动静态配置 HTTP 服务
-
-```powershell
-# 以管理员身份运行(80 端口可能需要管理员权限)
-powershell -ExecutionPolicy Bypass -File D:\WorkSpace\project\chuanzhanServer\scripts\start_local_static.ps1
-```
-
-或手动启动:
-
-```powershell
-python -m http.server 80 --directory D:\WorkSpace\project\chuanzhanServer\local_static\port80
-python -m http.server 8000 --directory D:\WorkSpace\project\chuanzhanServer\local_static\port8000
-```
-
-### 第三步:以管理员身份启动游戏服
-
-```powershell
-cd server\src\_output.server\net5.0
-OpenCards.Server.Main.exe local
-```
-
-Windows 下 HTTP 监听 `http://+:18081` 等需 URL ACL,可手动注册:
-
-```powershell
-netsh http add urlacl url=http://+:18081/account/ user=Everyone
-netsh http add urlacl url=http://+:18082/pay/ user=Everyone
-netsh http add urlacl url=http://+:18088/api/ user=Everyone
-netsh http add urlacl url=http://+:18089/chat/ user=Everyone
-```
-
-### 第四步:验证端口
-
-```powershell
-netstat -ano | findstr "LISTENING" | findstr ":80 :8000 :18081 :18082 :18088 :19821"
-```
-
----
-
-## 3. 起服异常:AccountServer 拉取静态配置失败
-
-### 3.1 现象
-
-```
-ERROR AccountServer - 由于目标计算机积极拒绝,无法连接。 (127.0.0.1:80),url:
-                                http://127.0.0.1/cjw_60000_stop_server.json
-
-System.Net.Http.HttpRequestException : 由于目标计算机积极拒绝,无法连接。 (127.0.0.1:80)
-```
-
-### 3.2 原因
-
-**不是游戏服没起来**,而是 AccountServer 启动后会 **主动 HTTP 拉取外部静态 JSON**,本机 80 / 8000 没有 HTTP 服务时会报「积极拒绝连接」。
-
-`_launch_server_local.xml` 相关配置:
-
-```xml
-<StopServerDataURL>http://127.0.0.1/cjw_60000_stop_server.json</StopServerDataURL>
-<ServerListUrl>http://127.0.0.1:8000/serverlist.json</ServerListUrl>
-<UpdateServerConfig>http://127.0.0.1/update_server_config.json</UpdateServerConfig>
-<!-- 以及 android/ios 版本记录配置 -->
-```
-
-| URL | 需要的本地服务 |
-|-----|----------------|
-| `http://127.0.0.1:8000/serverlist.json` | 8000 端口静态文件服务 |
-| `http://127.0.0.1/xxx.json`(无端口 = 80) | 80 端口静态文件服务 |
-
-### 3.3 本地静态配置目录
-
-项目已准备:
-
-| 端口 | 目录 | 说明 |
-|------|------|------|
-| 80 | `local_static/port80/` | 停服、更新、版本记录等 JSON |
-| 8000 | `local_static/port8000/` | 区服列表 `serverlist.json` |
-
-启动脚本:`scripts/start_local_static.ps1`
-
-### 3.4 备选:改用 OSS 远程地址
-
-不想搭本地静态服时,参考 `_launch_server_dev.xml` 将 URL 改为 OSS:
-
-```xml
-<StopServerDataURL>https://zqtfcn.oss-cn-shanghai.aliyuncs.com/apollo/cjw_60000_stop_server.json</StopServerDataURL>
-<ServerListUrl>https://zqtfcn.oss-cn-shanghai.aliyuncs.com/apollo/serverlist.json</ServerListUrl>
-```
-
-使用远程列表时,`address` 仍需改成本机 Connector 地址。
-
----
-
-## 4. Unity 客户端连接服务器流程
-
-整体链路如下:
-
-```
-┌─────────────────────────────────────────────────────────────────┐
-│  Unity 客户端                                                    │
-├─────────────────────────────────────────────────────────────────┤
-│  热更/版本检查 → LoginMgr.login_url(账号服 HTTP 地址)          │
-│       ↓                                                          │
-│  获取区服列表(见下方两条链路)                                   │
-│       ↓                                                          │
-│  账号登录 → s2c_gateUrl(SDK 模式)或 serverlist.address(DEBUG)│
-│       ↓                                                          │
-│  loginCtrl.EnterServer → LoginMgr.EnterServerRequest(address)    │
-│       ↓                                                          │
-│  connector.Connect(address)  →  Connector TCP(19821/19822)     │
-└─────────────────────────────────────────────────────────────────┘
-```
-
-### 4.1 区服列表的两条独立链路
-
-| 链路 | 触发方 | 数据来源 | 影响范围 |
-|------|--------|----------|----------|
-| **链路 A:DEBUG 登录** | `LoginMgr.GetServerUrl()` | Unity C# 硬编码/配置的 URL | `loginCtrl.serverlist_data` → `address` |
-| **链路 B:账号服** | AccountServer 启动拉取 | `_launch_server.xml` 的 `ServerListUrl` | 登录返回 `s2c_gateUrl` |
-
-**只改仓库里的 JSON 文件,不等于 Unity 一定会用到**——必须让对应链路的 URL 指向你的 JSON,且静态 HTTP 服务在运行。
-
-### 4.2 DEBUG 模式 vs SDK 模式
-
-| 模式 | 判定 | address 来源 |
-|------|------|-------------|
-| **DEBUG** | `loginCtrl.LOGINDEBUG = true`(`PLAT_ID == NORAML` 时默认 true) | `LoginMgr.GetServerUrl()` → `serverlist_data[].address` |
-| **SDK** | 走第三方/平台登录 | 账号登录响应 `s2c_gateUrl` ← AccountServer 从 serverlist 读出 |
-
-SDK 模式下账号服赋值逻辑:
-
-```csharp
-// AccountServer.HandleLogin.cs
-rsp.s2c_gateUrl = serverInfo.address;
-```
-
----
-
-## 5. `address=` 日志在哪里打印
-
-连接地址会在 **Lua 和 C# 两层** 打印。
-
-### 5.1 Lua 层
-
-文件:`server/src/data/ClientScript/gameControl/login/loginCtrl.lua`
-
-```lua
-function loginCtrl.EnterServer(uuid, token, serverID, address)
-    if not address then
-        -- 从 serverlist_data 按 serverid 查找
-        for i = 1, #loginCtrl.serverlist_data do
-            local v = loginCtrl.serverlist_data[i]
-            if v.serverid == tonumber(serverID) then
-                address = v.address
-                break
-            end
-        end
-    end
-    print("address=" .. address)   -- ← 此处打印
-    LoginMgr.EnterServerRequest(address, uuid, token, ...)
-end
-```
-
-调用入口(DEBUG 模式):
-
-- `loginView.lua` → `InitCompInDEBUG()` → 选服后 `loginCtrl.EnterServer(...)`
-
-### 5.2 C# 层(Unity)
-
-文件:`jyyz_game/client/Assets/Scripts/Common/LoginMgr.cs`
-
-```csharp
-public static void EnterServerRequest(string address, string account, string token, int sessionId, LuaFunction callBack)
-{
-    Debug.Log("EnterServerRequest=address=" + address);  // ← Unity 控制台也会打印
-    connector.Connect(address, 15000, new ClientEnterServerRequest() { ... });
-}
-```
-
-若看到 `address=127.0.0.1:19822`,说明客户端拿到的区服列表里该服的 `address` 字段就是 `127.0.0.1:19822`。
-
-### 5.3 关于 `19822` 端口
-
-- `local` 单节点:Connector 默认 **19821**(`_launch_server_local.xml`)
-- `local2` 多节点:2 区 Connector 为 **19822**(`_launch_server_local2.xml`)
-
-若选了 `serverid=2` 或 serverlist 中 2 区写了 `19822`,日志就会显示该地址。需与当前实际起服配置和监听端口一致。
-
----
-
-## 6. 为什么本地 `serverlist.json` 没生效
-
-### 6.1 链路 A:Unity DEBUG 模式(最常见)
-
-`LoginMgr.GetServerUrl` 从 **Unity 工程 C# 里配置的 URL** 下载列表,**不会自动读** 服务端仓库里的文件。
-
-当前客户端实现(`LoginMgr.cs`):
-
-```csharp
-public static void GetServerUrl(bool isDebug, LuaFunction callBack)
-{
-    // string url = DataPathHelper.server_list;  // 已注释
-    string url = "http://192.168.0.85/serverlist.json";  // ← 硬编码 URL
-    string returnText = wc.DownloadString(url);
-    // 包装为 {"servers": [...]} 后回调 Lua
-    callBack.call(serverData);
-}
-```
-
-**要让本地 serverlist 生效,需修改此处 URL**,例如:
-
-```csharp
-string url = "http://127.0.0.1:8000/serverlist.json";
-// 或本机 80 端口:http://127.0.0.1/serverlist.json
-```
-
-并确保对应端口的静态 HTTP 服务已启动。
-
-### 6.2 链路 B:账号服(影响 `s2c_gateUrl`)
-
-账号服从 `_launch_server.xml` 的 `ServerListUrl` 拉取:
-
-```xml
-<!-- _launch_server_local.xml -->
-<ServerListUrl>http://127.0.0.1:8000/serverlist.json</ServerListUrl>
-```
-
-生效条件:
-
-1. **8000 端口静态 HTTP 在运行**
-2. **游戏服已重启**(AccountServer 启动时拉取并缓存)
-3. **`address` 字段正确**(Connector TCP 端口,非 HTTP 端口)
-
-可通过账号服接口验证缓存内容:
-
-```
-http://127.0.0.1:18081/account/getserverlist
-```
-
-AccountServer 日志中也可看到:
-
-```
-GetServerList 服务器列表地址 : http://127.0.0.1:8000/serverlist.json
-GetServerList content : 1,true,127.0.0.1:19821,...
-```
-
-### 6.3 `serverlist.json` 正确写法
-
-```json
-[
-  {
-    "id": 1,
-    "index": 0,
-    "name": "本地1区",
-    "address": "127.0.0.1:19821",
-    "state": 1,
-    "is_open": true,
-    "capacity": 2000,
-    "serverid": 1,
-    "groupid": 1,
-    "note": "local"
-  }
-]
-```
-
-字段注意:
-
-| 字段 | 要求 |
-|------|------|
-| `address` | `IP:端口`,**不要** `http://`,**不要** 尾部 `/` |
-| `address` 端口 | 必须是 Connector(19821),**不是** 18081 |
-| `id` | 账号服用 `id` 做 map key,需与登录时 `serverid` 对应 |
-| `is_open` | `true` 才会进入有效区服列表 |
-
-### 6.4 错误示例
-
-```json
-"address": "192.168.0.85:18081/"
-```
-
-问题:
-
-- `18081` 是账号 HTTP 接口,不是游戏 TCP 入口
-- 尾部 `/` 可能导致地址解析异常
-
----
-
-## 7. 验证清单
-
-### 7.1 端口监听
-
-```powershell
-netstat -ano | findstr "LISTENING" | findstr ":80 :8000 :18081 :18082 :18088 :19821 :19822"
-```
-
-| 端口 | 期望 |
-|------|------|
-| 80 | LISTENING(静态配置) |
-| 8000 | LISTENING(serverlist) |
-| 18081 | LISTENING(AccountServer) |
-| 19821 | LISTENING(Connector,local 模式) |
-| 19822 | LISTENING(仅 local2 的 2 区) |
-
-### 7.2 静态配置可访问
-
-```powershell
-Invoke-WebRequest -Uri "http://127.0.0.1/cjw_60000_stop_server.json" -UseBasicParsing
-Invoke-WebRequest -Uri "http://127.0.0.1:8000/serverlist.json" -UseBasicParsing
-```
-
-### 7.3 账号服区服列表
-
-```powershell
-Invoke-WebRequest -Uri "http://127.0.0.1:18081/account/getserverlist" -UseBasicParsing
-```
-
-确认返回 JSON 中 `address` 为 `127.0.0.1:19821`(或你期望的 Connector 地址)。
-
-### 7.4 客户端实际连接地址
-
-Unity 控制台搜索:
-
-- Lua:`address=`
-- C#:`EnterServerRequest=address=`
-
-两者应一致,且与 `netstat` 中 Connector 监听端口匹配。
-
----
-
-## 8. 常见误区
-
-| 误区 | 说明 |
-|------|------|
-| 只改仓库 JSON,不改 Unity `LoginMgr.cs` | DEBUG 模式走 `GetServerUrl` 硬编码 URL,不读本地文件 |
-| `address` 写成 `18081` | 那是 HTTP 账号接口,客户端 TCP 应连 19821 |
-| 只改 JSON 不重启游戏服 | AccountServer 启动时拉取一次 serverlist 并缓存 |
-| 8000 静态服未启动 | `ServerListUrl` 指向 8000 但无进程监听 → 拉取失败 |
-| 起服配置与 serverlist 端口不一致 | `local` 用 19821,`local2` 二区用 19822,需对应 |
-| 混淆 80 报错与客户端连不上 | 80 是 AccountServer 拉停服配置;客户端连的是 Connector TCP |
-
----
-
-## 9. 关键文件索引
-
-### 服务端
-
-| 主题 | 路径 |
-|------|------|
-| 本地起服配置 | `server/src/server/OpenCards.Server.Main/_launch_server_local.xml` |
-| 多节点本地配置(含 19822) | `server/src/server/OpenCards.Server.Main/_launch_server_local2.xml` |
-| OSS 配置参考 | `server/src/server/OpenCards.Server.Main/_launch_server_dev.xml` |
-| AccountServer 拉取 serverlist | `server/src/server/OpenCards.Server.Account/Modules/AccountServer.HanldeServerListData.cs` |
-| 登录返回 gateUrl | `server/src/server/OpenCards.Server.Account/Modules/AccountServer.HandleLogin.cs` |
-| 本地静态配置(80) | `local_static/port80/` |
-| 本地静态配置(8000) | `local_static/port8000/` |
-| 静态服启动脚本 | `scripts/start_local_static.ps1` |
-| 配置样例 | `apollo_60000/` |
-
-### 客户端 Lua
-
-| 主题 | 路径 |
-|------|------|
-| 打印 `address=` | `server/src/data/ClientScript/gameControl/login/loginCtrl.lua` |
-| DEBUG 选服登录 | `server/src/data/ClientScript/gameView/login/loginView.lua` |
-| 热更设置 login_url | `server/src/data/ClientScript/HotUpdate/HotUpdateCtrl.lua` |
-
-### Unity C#
-
-| 主题 | 路径 |
-|------|------|
-| 拉取 serverlist(DEBUG) | `jyyz_game/client/Assets/Scripts/Common/LoginMgr.cs` → `GetServerUrl` |
-| TCP 连接 Connector | `jyyz_game/client/Assets/Scripts/Common/LoginMgr.cs` → `EnterServerRequest` |
-
----
-
-## 10. 总结
-
-| 问题 | 结论 |
-|------|------|
-| 起服报 `127.0.0.1:80` 拒绝 | 需独立静态 HTTP(80/8000),游戏服不提供这些 JSON |
-| `address=` 在哪打印 | Lua `loginCtrl.EnterServer` + C# `LoginMgr.EnterServerRequest` |
-| 本地 serverlist 不生效 | DEBUG 改 `LoginMgr.GetServerUrl` URL;SDK 改 `ServerListUrl` + 启静态服 + 重启游戏服 |
-| 客户端应连哪个端口 | Connector TCP:**19821**(local)或 **19822**(local2 二区),不是 18081 |
-| 推荐本地联调顺序 | 静态服(80/8000) → 游戏服 → 改 Unity URL → 验证 getserverlist → 看 `address=` 日志 |
-
----
-
-*文档由 Cursor 对话自动整理生成*

+ 0 - 53
docs/文档/环境搭建.md

@@ -1,53 +0,0 @@
-# Workspace.sln 环境搭建
-
-> 下载Visual Studio 2022/2019版本,需要注意的是,在visual studio install中,需要在工作负荷选项卡中勾选 ".NET桌面开发"和"使用Unity的游戏开发"。
-
-#### 下载.NET SDK 5.0或者其他版本,一般会有安装包,没有安装包的网上搜索下载即可。
-
-可以使用 https://dotnet.microsoft.com/zh-cn/download/dotnet/5.0 进行下载,下载安装后,使用windos键+R键,输入CMD,之后输入dotnet --info即可看到.NET SDK的版本信息。
-
-
-
-## 数据库和缓存
-
-#### MySQL 5.7
-
-MySQL下载路径:https://dev.mysql.com/downloads/installer/
-
-直接下载跟着步骤安装即可。
-
-安装成功后,安装并和谐Navicat Premium 16,连上mysql数据库即可。
-
-mysql启动命令:
-
-net start mysql57
-
-mysql -uroot -p1234
-
-直接使用exit退出
-
-net stop mysql57
-
-
-
-#### redis:7.0
-
-Redis安装参考文档:Redis7.0安装.md
-
-
-
-##### mongo:4.0
-
-MongoDB安装参考:MongoDB4.0安装.md
-
-
-
-## .NET打包
-
-OpenCards.Server.Main 是旧的 Windows 入口点项目,与 OpenCards.Server.DotNetCore 是平行的两套入口,如果需要使用Linux运行,linux使用的OpenCards.Server.DotNetCore.dll 这个文件。
-
-dotnet publish OpenCards.Server.DotNetCore.csproj -c Release -r linux-x64 --self-contained false -o ./_output.server
-
-
-
-打包之后 OpenCards.Server.DotNetCore.csproj大小写不对,检查发现是打包命令出错,需要改成dotnet publish OpenCards.Server.DotNetCore.csproj  xxx ,生成的文件大小写才会对应。

+ 0 - 407
docs/文档/配置表修改.md

@@ -1,407 +0,0 @@
-# 配置表体系总结
-
-> 项目路径:`D:\WorkSpace\project\chuanzhanServer`  
-> 配置表源文件目录:`server/src/data/ServerData/templates_xls/`  
-> 本文仅做说明,**未修改任何配置表源文件**。
-
----
-
-## 1. 整体架构
-
-配置表采用 **Excel 源文件 → Lua 中间数据 → 客户端/服务端分别读取** 的三层结构:
-
-```mermaid
-flowchart LR
-    A[templates_xls/*.xlsx<br/>Excel 源文件] -->|xlslang 导表| B[templates_lua/<br/>服务端 Lua 数据]
-    A -->|xcopy 同步| C[ClientScript/Data/<br/>客户端 Lua 数据]
-    B --> D[服务端 C#<br/>TableManager + Table_XXX]
-    C --> E[客户端 Lua<br/>DataCenter + DataTag]
-```
-
-| 层级 | 路径 | 用途 |
-|------|------|------|
-| 源文件 | `server/src/data/ServerData/templates_xls/` | 策划编辑的 Excel |
-| 服务端数据 | `server/src/data/ServerData/templates_lua/` | 服务端运行时读取 |
-| 客户端数据 | `server/src/data/ClientScript/Data/` | Unity 客户端运行时读取 |
-| 分地区数据 | `templates_xls_area/` → `templates_lua_area/` → `ClientScript/DataArea/` | 按地区(cn/en 等)差异化配置 |
-| 多语言 | `lang/lang.csv` | 由 `build_xls2lang.bat` 从 Excel 导出 |
-
-**重要**:服务端**不会**读 `ClientScript/Data/`,两套 Lua 目录由导表脚本并行维护,内容应保持一致。
-
----
-
-## 2. 命名规范
-
-### 2.1 Excel 文件名
-
-格式:**`中文描述#英文标识.xlsx`**
-
-| 示例源文件 | `#` 后英文标识 | 导出目录 |
-|-----------|---------------|---------|
-| `C常量配置#gameConfig.xlsx` | `gameConfig` | `templates_lua/gameConfig.xlsx/` |
-| `X新手任务#activity_new.xlsx` | `activity_new` | `templates_lua/activity_new.xlsx/` |
-| `D道具表#item.xlsx` | `item` | `templates_lua/item.xlsx/` |
-
-- `#` 前:中文说明,便于策划识别
-- `#` 后:英文标识,决定 Lua 输出目录名及代码中的文件引用
-- 代码中统一使用 **`#` 后的英文名 + `.xlsx`**,如 `"gameConfig.xlsx"`、`"activity_new.xlsx"`
-
-### 2.2 Sheet(页签)名
-
-- 每个 Sheet 导出一个同名 `.lua` 文件
-- 示例:`gameConfig.xlsx` 的 `gameConfig` Sheet → `gameConfig.xlsx/gameConfig.lua`
-- 一个 Excel 可含多个 Sheet,对应多个 Lua 文件
-
-### 2.3 目录分类(参考 `!!!表目录解释.txt`)
-
-| 目录/前缀 | 用途 |
-|----------|------|
-| `config/` | 全局配置(GM 权限、服务器配置等) |
-| `event/` | 事件库配置 |
-| `gameSystem/` | 核心通用配置(背包、装备、道具等) |
-| `localization/` | 多语言(导 lua 时过滤,走 `build_xls2lang.bat`) |
-| `logicData/` | UI 相关业务逻辑 |
-
-### 2.4 被导表工具忽略的 Sheet
-
-`build_xls2lua.bat` 中 `filter_text` 会排除:
-
-- 以 `~` 开头的 Sheet
-- `localization/` 下的 Sheet
-- `clx/battle/func/` 下的 Sheet
-
----
-
-## 3. Excel 表结构约定
-
-详细规范见 `!!!数据表简单说明,建表必看.docx`(内含外部文档链接)。从现有导出结果可归纳:
-
-### 3.1 数据 Sheet 结构
-
-- **第 2 行(程序读表头)**:英文字段名(对应 Lua `_key_` 与 C# 字段名)
-- **第 4 行起**:数据行(`tool.py` 从第 4 行开始读,`xlslang` 遵循同类约定)
-
-导出 Lua 示例(`activity_task.lua`):
-
-```lua
-return {
-  ["_key_"] = {"Id","Type","StageName","Target","Desc","DropGroupId",},
-  [1] = {1, 2, "activity_task_1.StageName", 25, "activity_task_1.Desc", 1102001,},
-}
-```
-
-- `_key_`:字段名列表
-- 数字键 `[1]`、`[2]`…:数据行
-- 字符串值若为多语言 key,会按 `lang_format` 生成文档 ID
-
-### 3.2 配置规范 Sheet
-
-部分表含 `*配置规范` Sheet,描述字段元信息(中文名、字段名、类型、说明),供策划与工具校验,例如:
-
-```
-["_key_"] = {"编号","Id","NUMBER","主键",}
-```
-
----
-
-## 4. 导表流程(Excel → Lua)
-
-### 4.1 全量导表
-
-在 `server/src/data/ServerData/` 下执行:
-
-```bat
-build_xls2lua.bat
-```
-
-主要步骤:
-
-1. 合并 `templates_xls_area/` 到临时目录
-2. `xlslang lua` 将 `templates_xls/` 转为 `templates_lua/`
-3. `xlslang md5` 生成 `_luaversion_.lua`(各文件 MD5,用于热更/version 对比)
-4. 将 `templates_lua/*.lua` 复制到 `../ClientScript/Data`
-5. 分地区 Lua 复制到 `../ClientScript/DataArea`
-
-核心命令:
-
-```bat
-xlslang lua -id:.\templates_xls -od:.\templates_lua -key:id -olang:1 ^
-  -filter_text:"-~;-localization/;-clx/battle/func/" ^
-  -lang_format:"<doc><PATH><FILE><SHEET_NAME/></FILE><DATA_TYPE/></PATH>_<ROW_ID/>.<COLUMN_NAME/></doc>"
-```
-
-### 4.2 增量导表
-
-为加快迭代,可按表名筛选,例如:
-
-| 脚本 | 范围 |
-|------|------|
-| `build_xls2lua_battle.bat` | `*#battle*` |
-| `build_xls2lua_ly.bat` | Stage、tower、hero、item 等 |
-| `build_xls2lua_gxy.bat` | `*#item.xlsx` |
-
-### 4.3 多语言导表
-
-```bat
-build_xls2lang.bat   # 或 build_xls2lang1.bat / build_xls2lang2.bat
-```
-
-输出到 `lang/lang.csv`,供客户端 Localization 使用。
-
----
-
-## 5. 服务端:如何读取与使用
-
-### 5.1 初始化链路
-
-```csharp
-// Program.cs 启动顺序(不可打乱)
-new LuaTemplateLoader(true, ad);           // 必须为 true
-new LuaDataCenter(ad, ".../templates_lua");
-CardsServerTemplateManager.Instance.Init(); // → TableManager.LoadAllConfig
-```
-
-数据根目录由 `CardsServerTemplateManager.ResolveServerDataRoot()` 解析,通常为 `server/src/data/ServerData/`。
-
-### 5.2 C# 表定义
-
-在 `OpenCards.Server.Core/Table/` 下定义表类:
-
-```csharp
-[Table("activity_new.xlsx", "activity_task")]
-public class Table_ActivityTask : Table
-{
-    [TableMember(false, true)]  // key 主键
-    public int Id;
-    public int Type;
-    public int Target;
-    public int DropGroupId;
-}
-```
-
-`[TableMember(unionkey, key, index)]` 含义:
-
-| 参数 | 含义 |
-|------|------|
-| `key = true` | 唯一主键,生成 `XxxMap` 与 `GetByXxx()` |
-| `index = true` | 索引,一对多,生成 `GetListByXxx()` |
-| `unionkey = true` | 联合主键(多个 unionkey 组合) |
-
-### 5.3 自动生成 TableManager
-
-运行 `OpenCards.Server.Tools` 会扫描带 `[Table]` 的类,生成 `TableManager.cs`,包含:
-
-- `ListenLoader` 注册
-- `LoadTemplates<TKey, TTable>()`
-- `GetByXxx()` / `GetListByXxx()` 等访问方法
-
-**新增/删除服务端表后,需重新运行 Tools 生成 `TableManager.cs`。**
-
-### 5.4 业务代码用法
-
-```csharp
-// 按主键查
-var task = Table_ActivityTaskManager.GetById(1);
-
-// 常量表
-var val = Table_ConstantManager.GetByName("FormationMaxCount");
-
-// 按索引查列表
-var drops = Table_DropGroupManager.GetListByDropGroupID(dropGroupId);
-```
-
-常量类 `ConstantConfig` 等通过 `CustomTableManager.cs` 在 `OnReload` 时把表数据映射为静态字段。
-
-### 5.5 表校验
-
-可在 `CheckTable()` 中做跨表引用校验,服务启动时由 `CardsServerTemplateManager.CheckTables` 统一执行。
-
----
-
-## 6. 客户端:如何读取与使用
-
-### 6.1 初始化
-
-`Startup.lua` 中:
-
-```lua
-DataTag = require "gameModel/DataTag"
-DataCenter.Init(require("gameModel/modelTag"), require("gameModel/DataTag"), helper.GetProductLineArea())
-```
-
-### 6.2 DataTag 映射
-
-`gameModel/DataTag.lua` 将逻辑名映射到 Lua 路径:
-
-```lua
-ActivityCrewGather = "activity_new.xlsx/activity_task",
-GameConfig = "gameConfig.xlsx/gameConfig",
-HeroConfig = "hero.xlsx/heroconfig",
-```
-
-**新增客户端可读表时,需在 `DataTag.lua` 增加条目。**
-
-### 6.3 查询 API
-
-```lua
--- 条件查询(返回列表)
-local list = DataCenter.DB.Find(DataTag.ActivityCrewGather, {Type = 2})
-
--- 查首条
-local hero = DataCenter.DB.FindFirst(DataTag.HeroConfig, {ID = heroId})
-
--- 整表
-local all = DataCenter.DB.GetFullTable(DataTag.HeroConfig)
-
--- 常量 gameConfig
-local maxCount = DataCenter.DB.GetGameConfig("FormationMaxCount")
-```
-
-底层通过 `Core/DataCenter.lua` 的 `require` 加载 Lua,`make_refs` 建索引,并支持只读保护。
-
-### 6.4 分地区配置
-
-`DataCenter.Init` 会加载 `DataArea/{area}/_luaversion_`,若表存在于地区目录,优先读 `DataArea/` 下的版本。
-
----
-
-## 7. 配置表增删改操作指南
-
-### 7.1 新增配置表
-
-| 步骤 | 操作 |
-|------|------|
-| 1 | 在 `templates_xls/` 新建 `中文名#英文名.xlsx` |
-| 2 | 按规范建 Sheet,填字段名与数据 |
-| 3 | 执行 `build_xls2lua.bat`(或增量脚本) |
-| 4 | **服务端**:新增 `Table_XXX.cs`,标注 `[Table]`,运行 `OpenCards.Server.Tools` 生成 `TableManager.cs` |
-| 5 | **客户端**:在 `DataTag.lua` 增加映射 |
-| 6 | 可选:在 `CustomTableManager.cs` 增加 `Init()` 做二次加工 |
-
-### 7.2 修改配置表
-
-| 步骤 | 操作 |
-|------|------|
-| 1 | 修改 `templates_xls/` 中 Excel(**不要直接改 `.lua`**,文件头有 “do not edit” 警告) |
-| 2 | 重新执行导表脚本 |
-| 3 | 若新增/删除/重命名字段,同步更新 C# `Table_XXX` 与 `DataTag` |
-| 4 | 触发热更新(见第 8 节) |
-
-### 7.3 删除配置表
-
-| 步骤 | 操作 |
-|------|------|
-| 1 | 删除或禁用 Excel 中对应 Sheet/文件 |
-| 2 | 重新导表 |
-| 3 | 删除 C# `Table_XXX.cs`,重新生成 `TableManager.cs` |
-| 4 | 删除 `DataTag.lua` 中对应项 |
-| 5 | 清理业务代码中的引用 |
-
----
-
-## 8. 变更后如何“实时”读取
-
-### 8.1 服务端热更新
-
-服务端通过 `TemplateDataCenter.ListenLoader` 监听 `templates_lua/` 下文件变化:
-
-```
-TemplateDataCenter - Reload : CacheData : File=templates_lua/activity_new.xlsx
-```
-
-**典型开发流程**:
-
-1. 改 Excel → 运行 `build_xls2lua.bat` → 更新 `templates_lua/`
-2. 若服务已启动,`ListenLoader` 检测到 Lua 变更后自动 Reload
-3. 每张表 Reload 时调用 `table.OnReload()`,并触发 `OnLoad` 回调(如 `ConstantConfig` 刷新)
-
-**手动 Reload**:
-
-| 方式 | 命令/接口 | 说明 |
-|------|----------|------|
-| GM | `reloadtable templates_lua/xxx.xlsx` | 重载指定表 |
-| GM | `reloadalltable` | 重载全部(`CardsServerTemplateManager.Instance.Init()`) |
-| 控制台 | `reloadtable <path>` | `OpenCardsRpcAppFactory.cs` 中 `CMD_RELOADTABLE` |
-| 代码 | `CardsServerTemplateManager.Instance.ReloadTable(path)` | 编程调用 |
-
-**注意**:仅改 Excel 不导表,或服务读的不是 `ServerData/templates_lua/`,则不会生效。
-
-### 8.2 客户端热更新
-
-客户端从 `ClientScript/Data/` 读 Lua,机制与服务端不同:
-
-| 机制 | 说明 |
-|------|------|
-| 导表同步 | `build_xls2lua.bat` 会把 Lua 复制到 `ClientScript/Data/` |
-| 内存缓存 | `DataCenter` / `__globalhooks_datacenter.cache` 缓存已加载表 |
-| 清缓存 | `__globalhooks_datacenter.clear_cache()` / `remove_cache(path)` |
-| 版本文件 | `_luaversion_.lua` 记录各文件 MD5 |
-| 在线同步 | `ClientInit.lua` 中 `SyncExcelData` **已注释**,当前主要靠重新导表 + 重启/重载资源 |
-
-**开发期客户端生效方式**:
-
-1. 改 Excel → 导表 → 确认 `ClientScript/Data/` 已更新
-2. Unity 中重启或触发资源/Lua 重载
-3. 或在运行时调用 `clear_cache` 后重新 `Find`
-
-Editor 下若配置了 Lua 热重载,改 `ClientScript` 下 Lua 可能即时生效,但**规范做法仍是改 Excel 再导表**,避免手改 Lua 被覆盖。
-
----
-
-## 9. 客户端 vs 服务端差异
-
-| 维度 | 服务端 | 客户端 |
-|------|--------|--------|
-| 数据目录 | `ServerData/templates_lua/` | `ClientScript/Data/` |
-| 访问方式 | C# `Table_XXXManager` | Lua `DataCenter.DB.Find(DataTag.xxx)` |
-| 类型定义 | `Table/Table_XXX.cs` | 无强类型,按 `_key_` 动态解析 |
-| 字段差异 | 可只定义服务端需要的字段 | 可读 Excel 中全部字段(含 `StageName`、`Desc` 等展示字段) |
-| 热更新 | `ListenLoader` + GM/控制台 Reload | 需清缓存或重启;在线 Sync 暂未启用 |
-| 常量 | `ConstantConfig` 静态字段 | `DataCenter.DB.GetGameConfig()` |
-
-服务端日志中 `Field not found ... StageName/Desc` 属于正常 WARN:Lua 有字段,C# 类未声明,会被忽略。
-
----
-
-## 10. 常用文件与脚本索引
-
-| 文件 | 作用 |
-|------|------|
-| `templates_xls/` | Excel 源文件 |
-| `build_xls2lua.bat` | 全量 Excel → Lua |
-| `build_xls2lang.bat` | 多语言导出 |
-| `templates_lua/_luaversion_.lua` | 文件 MD5 版本 |
-| `TableManager.cs` | 服务端加载入口(自动生成) |
-| `Table/Table_*.cs` | 服务端表结构定义 |
-| `CustomTableManager.cs` | 常量/开关等二次映射 |
-| `CardsServerTemplateManager.cs` | 模板管理器 |
-| `gameModel/DataTag.lua` | 客户端表名映射 |
-| `Core/DataCenter.lua` | 客户端查表核心 |
-| `OpenCards.Server.Tools/Program.cs` | 生成 `TableManager.cs` |
-| `!!!表目录解释.txt` | 目录分类说明 |
-| `!!!数据表简单说明,建表必看.docx` | 建表规范(含外部文档链接) |
-
----
-
-## 11. 开发检查清单
-
-**改表后:**
-
-- [ ] 修改的是 `templates_xls/` 下的 Excel,而非 `.lua`
-- [ ] 已执行对应导表脚本
-- [ ] `templates_lua/` 与 `ClientScript/Data/` 均已更新
-- [ ] 新增/删字段已同步 C# `Table_XXX` 与 `DataTag.lua`
-- [ ] 新表已运行 `OpenCards.Server.Tools` 生成 `TableManager.cs`
-- [ ] 服务端:确认 `LuaTemplateLoader(true, ad)`,必要时 `reloadtable` 或 `reloadalltable`
-- [ ] 客户端:清缓存或重启 Unity 使新数据生效
-
-**启动服务端前:**
-
-- [ ] `server/src/data/ServerData/templates_lua/` 存在且完整
-- [ ] 工作目录能正确解析 `ServerData`(见 `ResolveServerDataRoot`)
-
----
-
-## 12. 总结
-
-本项目配置表以 **`templates_xls/` 为唯一权威源**,经 **`xlslang` 导表** 生成 Lua,服务端通过 **C# Table + TableManager** 读取,客户端通过 **DataTag + DataCenter** 读取。命名上 Excel 使用 **`中文#英文.xlsx`**,Sheet 名即 Lua 文件名。变更后需 **先导表再触发热更新**:服务端支持 **文件监听 + GM/控制台 Reload**;客户端主要依赖 **重新导表 + 重启/清缓存**,在线增量同步能力尚未启用。新增表时,服务端需补 **C# 表类并重新生成 TableManager**,客户端需补 **DataTag 映射**。

+ 0 - 163
docs/文档/项目概述.md

@@ -1,163 +0,0 @@
-# Workspace.sln 项目架构概述
-
-## 一、项目概览
-
-**OpenCards** 是一个基于 .NET / C# 的卡牌游戏服务端项目,采用 **微服务 + 分层架构** 设计。解决方案 `Workspace.sln` 包含 **26 个项目**,按功能划分为 **Core(核心库)、Client(客户端)、Server(服务端)、Publish(发布)、Tool(工具)** 五大模块。
-
-| 配置 | 值 |
-|------|-----|
-| 解决方案格式 | Visual Studio Solution File Format 12.00 |
-| VS 版本 | 2022 (17.8) |
-| 配置平台 | Debug / Release × Any CPU / x64 / x86 |
-| 项目总数 | 26 |
-
----
-
-## 二、顶层目录结构
-
-```
-Workspace.sln
-├── 1.Core          — 核心基础库(3 个项目)
-├── 2.Client        — 客户端项目(2 个项目)
-├── 3.Server        — 服务端项目(20 个项目)
-├── 4.Publish       — 发布/部署项目(1 个项目)
-└── 5.Tool          — 工具项目(1 个项目)
-```
-
-> **注意**:`OpenCards.Server.DotNetCore` 同时归属于 `3.Server` 和 `4.Publish`;`OpenCards.Service.Tool` 同时归属于 `3.Server` 和 `5.Tool`。
-
----
-
-## 三、模块详解
-
-### 3.1 Core — 核心基础库
-
-| 项目 | 路径 | 职责 |
-|------|------|------|
-| **OpenCards.Core** | `src/core/OpenCards.Core/` | 核心基础库,定义公共接口、实体、工具方法 |
-| **OpenCards.GenCodec** | `src/core/OpenCards.GenCodec/` | 代码生成与序列化编解码器 |
-| **OpenCards.Tools** | `src/core/OpenCards.Tools/` | 开发辅助工具集 |
-
-### 3.2 Client — 客户端
-
-| 项目 | 路径 | 职责 |
-|------|------|------|
-| **OpenCards.Client.Core** | `src/client/OpenCards.Client.Core/` | 客户端核心逻辑,与表现层无关 |
-| **OpenCards.Client.Win32** | `src/client/OpenCards.Client.Win32/` | Windows 桌面客户端(Win32) |
-
-> **未纳入解决方案的项目**(存于磁盘):
-> - `OpenCards.Client.Unity` — Unity 客户端
-
-### 3.3 Server — 服务端
-
-服务端是整个解决方案的核心,分为 **基础设施层**、**业务服务层**、**功能模块层** 三个子层:
-
-#### 3.3.1 基础设施层
-
-| 项目 | 路径 | 职责 |
-|------|------|------|
-| **OpenCards.Server.Core** | `src/server/OpenCards.Server.Core/` | 服务端核心框架,定义服务基类、生命周期 |
-| **OpenCards.Server.Common** | `src/server/OpenCards.Server.Common/` | 服务端公共模块,共享 DTO/工具类 |
-| **OpenCards.Server.Logic** | `src/server/OpenCards.Server.Logic/` | 核心业务逻辑层 |
-| **OpenCards.Server.Main** | `src/server/OpenCards.Server.Main/` | 服务端主入口,启动引导 |
-| **OpenCards.Server.GenORM** | `src/server/OpenCards.Server.GenORM/` | ORM 代码生成 |
-| **OpenCards.Server.Tools** | `src/server/OpenCards.Server.Tools/` | 服务端工具集 |
-| **OpenCards.Server.DotNetCore** | `src/server/OpenCards.Server.DotNetCore/` | .NET Core 适配/发布层 |
-
-#### 3.3.2 业务服务层(Service 微服务)
-
-| 项目 | 路径 | 职责 |
-|------|------|------|
-| **OpenCards.Service.Connector** | `src/server/OpenCards.Service.Connector/` | 连接管理服务(网关/长连接) |
-| **OpenCards.Service.Center** | `src/server/OpenCards.Service.Center/` | 中心调度服务 |
-| **OpenCards.Service.Public** | `src/server/OpenCards.Service.Public/` | 公共服务(公告/配置) |
-| **OpenCards.Service.Account** | `src/server/OpenCards.Server.Account/` | 账号服务(登录/注册/认证) |
-| **OpenCards.Service.Guild** | `src/server/OpenCards.Service.Guild/` | 公会/战队服务 |
-| **OpenCards.Service.Friend** | `src/server/OpenCards.Service.Friend/` | 好友/社交服务 |
-| **OpenCards.Service.Chat** | `src/server/OpenCards.Service.Chat/` | 聊天/消息服务 |
-| **OpenCards.Service.Admin** | `src/server/OpenCards.Service.Admin/` | 后台管理服务 |
-| **OpenCards.Service.Pay** | `src/server/OpenCards.Service.Pay/` | 支付/充值服务 |
-| **OpenCards.Service.Tool** | `src/server/OpenCards.Service.Tool/` | GM 工具/运维服务 |
-
-#### 3.3.3 功能模块层
-
-| 项目 | 路径 | 职责 |
-|------|------|------|
-| **OpenCards.Server.Bill** | `src/server/OpenCards.Server.Bill/` | 账单/流水模块 |
-| **OpenCards.Server.Arena** | `src/server/OpenCards.Server.ArenaValor/` | 竞技场/天梯模块 |
-| **OpenCards.Server.StageRank** | `src/server/OpenCards.Server.StageRank/` | 段位/赛季排名模块 |
-
-#### 3.3.4 单元测试
-
-| 项目 | 路径 | 职责 |
-|------|------|------|
-| **OpenCards.TestAll** | `src/server/OpenCards.TestAll/` | 集成/全量测试 |
-
-> **未纳入解决方案但存于磁盘的服务端项目**:
-> - `OpenCards.Server.Battle` — 战斗服务器
-> - `OpenCards.Server.BattleRecord` — 战斗记录服务
-> - `OpenCards.Server.ICE` — ICE 中间件服务
-> - `OpenCards.Server.NameServer` — 名字服务
-> - `OpenCards.Service.Room` — 房间管理服务
-> - `OpenCards.Service.Role` — 角色/英雄服务
-> - `OpenCards.Service.Rank` — 排行榜服务
-> - `OpenCards.Service.Patch` — 热更新/补丁服务
-
----
-
-## 四、架构图
-
-```
-┌─────────────────────────────────────────────────────────────────┐
-│                        5.Tool / 4.Publish                       │
-│              OpenCards.Service.Tool | Server.DotNetCore          │
-├─────────────────────────────────────────────────────────────────┤
-│                        3.Server (服务端)                         │
-│  ┌───────────────────────────────────────────────────────────┐  │
-│  │                    业务服务层 (Service)                     │  │
-│  │  Connector │ Center │ Public │ Account │ Guild │ Friend    │  │
-│  │  Chat │ Admin │ Pay │ Tool │ (Room/Role/Rank/Patch*)      │  │
-│  ├───────────────────────────────────────────────────────────┤  │
-│  │                    功能模块层 (Module)                      │  │
-│  │  Arena │ StageRank │ Bill │ (Battle/BattleRecord*)        │  │
-│  ├───────────────────────────────────────────────────────────┤  │
-│  │                    基础设施层 (Infra)                       │  │
-│  │  Server.Core │ Server.Main │ Server.Common │ Server.Logic  │  │
-│  │  Server.GenORM │ Server.Tools │ Server.DotNetCore         │  │
-│  │  Server.ICE* │ Server.NameServer*                          │  │
-│  └───────────────────────────────────────────────────────────┘  │
-├─────────────────────────────────────────────────────────────────┤
-│  2.Client                    │  1.Core                          │
-│  Client.Core │ Client.Win32  │  Core │ GenCodec │ Tools         │
-│  (Client.Unity*)             │                                   │
-└─────────────────────────────────────────────────────────────────┘
-  * 表示存于磁盘但未纳入 Workspace.sln 的项目
-```
-
----
-
-## 五、技术特征
-
-- **语言**:C# (.NET / .NET Framework)
-- **架构模式**:微服务 + 分层架构(DDD 风格的分包模式)
-- **通信方式**:ICE 中间件(从 `OpenCards.Server.ICE` 推断)、Connector 长连接网关
-- **数据持久化**:自有 ORM 层(`OpenCards.Server.GenORM`),自动生成数据访问代码
-- **序列化**:自定义 Codec(`OpenCards.GenCodec`),适用于高性能网络通信
-- **部署模式**:各 Service 独立部署,通过 Center 服务协调调度
-- **测试**:集中测试项目 `OpenCards.TestAll`
-
----
-
-## 六、关联项目
-
-项目根目录下还存在以下 **未纳入 Workspace.sln** 的独立解决方案:
-
-| 项目目录 | 说明 |
-|----------|------|
-| `DeepMMO/` | MMO 游戏框架(独立 sln,含 Client/Server/Area/Gate/Logic 等子项目) |
-| `src/plugin/` | 插件项目(Battle.Report、Battle.Report.Web) |
-| `Backup/` | 解决方案备份 |
-
----
-
-> 生成日期:2026-06-08

+ 0 - 216
docs/文档6.10/server_battle_SLua问题排查与打包记录.md

@@ -1,216 +0,0 @@
-# server_battle 项目 SLua 问题排查与打包记录
-
-> 整理时间:2026-06-10  
-> 项目路径:`D:\WorkSpace\project\chuanzhanServer\server_battle`
-
----
-
-## 一、`using SLua` 编译报错
-
-### 现象
-
-`Program.cs` 中 `using SLua;` 无法识别,编译失败。
-
-### 原因
-
-`GameLogic.csproj` 引用了 NuGet 包路径:
-
-```
-..\packages\slua-standalone.1.0.67\lib\net35\slua-standalone.dll
-```
-
-但仓库中 **缺少该 DLL**。虽然存在 `slua-standalone.1.0.67` 目录和 `Packages` 文件夹,但只有 `content`(配置文件、`.so` 等),没有 `lib\net35\slua-standalone.dll`。
-
-另外注意:`Packages`(大写)与 `packages`(小写)是两个不同目录,csproj 引用的是小写 `packages`。
-
-### 解决方案
-
-从 NuGet 下载并解压 `slua-standalone 1.0.67`:
-
-```powershell
-Invoke-WebRequest -Uri "https://www.nuget.org/api/v2/package/slua-standalone/1.0.67" -OutFile "slua-standalone.1.0.67.zip"
-Expand-Archive -Path "slua-standalone.1.0.67.zip" -DestinationPath "packages\slua-standalone.1.0.67"
-```
-
-解压后应存在:
-
-- `packages\slua-standalone.1.0.67\lib\net35\slua-standalone.dll` — C# 绑定(提供 `SLua` 命名空间)
-- `packages\slua-standalone.1.0.67\content\lib\x64\slua.dll` — Windows 原生库
-
-同时将原生库复制到 `GameLogic\lib\x64` 和 `GameLogic\lib\x86`。
-
-### 其他安装方式
-
-**Visual Studio 包管理器控制台:**
-
-```powershell
-Install-Package slua-standalone -Version 1.0.67
-```
-
-**或从同仓库其他项目复制:**
-
-`server\src\server\OpenCards.Server.Battle` 引用 `server\src\library\slua-standalone.dll`(若该路径有文件)。
-
-### 验证结果
-
-MSBuild 编译成功,生成 `GameLogic.exe`。
-
----
-
-## 二、打包输出到 `_output.battle.server`
-
-### 需求
-
-将当前项目打包,输出到 `_output.battle.server` 目录。
-
-### 项目特点
-
-本项目为 **.NET Framework 4.8 旧式项目**,不能使用 `dotnet publish`,需使用 MSBuild。
-
-### 已做调整
-
-1. **Release 输出路径**:与 Debug 一致,统一到 `_output.battle.server`
-2. **AfterBuild**:自动将 `GameLogic\lib` 复制到输出目录
-3. **打包.bat**:改为使用 MSBuild(原 `dotnet publish` 不适用)
-
-### 打包命令
-
-```bat
-MSBuild server_battle.sln /t:Rebuild /p:Configuration=Release
-```
-
-或直接双击 `打包.bat`。
-
-### 输出目录结构
-
-```
-_output.battle.server\
-  GameLogic.exe
-  GameLogic.exe.config
-  ServerLib.dll
-  Newtonsoft.Json.dll
-  slua-standalone.dll
-  slua.dll                    ← 后续修复新增
-  lib\
-    x64\slua.dll
-    x86\slua.dll
-    x64\slua.so
-    x86\slua.so
-```
-
-### 运行说明
-
-在 `_output.battle.server` 目录下放置 `Config.json`(含 `ip`、`port`、`luaRoot`),再运行 `GameLogic.exe`。
-
----
-
-## 三、运行时 `DllNotFoundException: 无法加载 DLL "slua"`
-
-### 现象
-
-```
-System.DllNotFoundException: 无法加载 DLL "slua": 找不到指定的模块。
-(异常来自 HRESULT:0x8007007E)
-```
-
-### 原因分析
-
-SLua 内部通过 P/Invoke 加载原生库:
-
-```csharp
-[DllImport("slua", CallingConvention = CallingConvention.Cdecl)]
-```
-
-**Windows 加载规则**:在 `GameLogic.exe` **同目录**、系统目录或 `PATH` 中查找 `slua.dll`,**不会**自动搜索 `lib\x64`、`lib\x86` 子目录。
-
-之前打包结果中 `slua.dll` 只在子目录:
-
-```
-_output.battle.server\
-  GameLogic.exe
-  lib\x64\slua.dll   ← Windows 找不到
-  lib\x86\slua.dll
-```
-
-### 额外问题:位数不匹配
-
-- 原项目为 `AnyCPU`,在部分环境下以 **32 位** 进程运行
-- 若复制 `lib\x64\slua.dll` 到根目录,会报 `BadImageFormatException`
-- 实测复制 `lib\x86\slua.dll` 可正常启动
-
-`slua-standalone.dll.config` 中的 `<dllmap>` **仅 Linux/Mono 有效**,在 Windows .NET Framework 下不起作用。
-
-### 解决方案
-
-修改 `GameLogic.csproj`:
-
-1. **平台改为 x64**,与 `lib\x64\slua.dll` 一致
-2. **构建后自动复制** 对应架构的 `slua.dll` 到 `_output.battle.server\slua.dll`(与 exe 同级)
-
-AfterBuild 逻辑示意:
-
-```xml
-<PropertyGroup>
-  <SluaNativeArch Condition="'$(PlatformTarget)' == 'x86'">x86</SluaNativeArch>
-  <SluaNativeArch Condition="'$(PlatformTarget)' == 'x64'">x64</SluaNativeArch>
-  <SluaNativeArch Condition="'$(SluaNativeArch)' == ''">x64</SluaNativeArch>
-</PropertyGroup>
-<Copy SourceFiles="$(ProjectDir)lib\$(SluaNativeArch)\slua.dll"
-      DestinationFiles="$(OutputPath)slua.dll" />
-```
-
-### 手动临时修复(不重新编译)
-
-```powershell
-# 64 位进程(项目已改为 x64)
-Copy-Item "GameLogic\lib\x64\slua.dll" "_output.battle.server\slua.dll"
-
-# 若进程是 32 位
-Copy-Item "GameLogic\lib\x86\slua.dll" "_output.battle.server\slua.dll"
-```
-
-### 验证结果
-
-重新打包后服务正常启动:
-
-```
-luaRootPath->D:\WorkSpace\project\chuanzhanServer\server\src\data\ClientScript\
-Sever is running at http://127.0.0.1:8088
-```
-
----
-
-## 四、注意事项汇总
-
-| 项目 | 说明 |
-|------|------|
-| 目标框架 | .NET Framework 3.5+(当前 4.8,满足要求) |
-| NuGet 包路径 | csproj 引用小写 `packages`,勿与 `Packages` 混淆 |
-| Windows 原生库 | `slua.dll` 必须与 `GameLogic.exe` 同目录 |
-| 平台位数 | 建议明确使用 x64,避免 AnyCPU 位数不一致 |
-| Linux 部署 | 需 `slua.so`,设置 `LD_LIBRARY_PATH` 或使用 dllmap 配置 |
-| VC++ 运行库 | 若仍报「找不到模块」,可能需安装 Visual C++ Redistributable |
-| Git 提交 | 建议将 `packages\slua-standalone.1.0.67\lib\` 纳入版本库 |
-
----
-
-## 五、相关文件路径
-
-| 文件 | 路径 |
-|------|------|
-| 主程序 | `GameLogic\Program.cs` |
-| 项目配置 | `GameLogic\GameLogic.csproj` |
-| SLua 原生库 | `GameLogic\lib\x64\slua.dll`、`GameLogic\lib\x86\slua.dll` |
-| SLua C# 绑定 | `packages\slua-standalone.1.0.67\lib\net35\slua-standalone.dll` |
-| 输出目录 | `_output.battle.server\` |
-| 打包脚本 | `打包.bat` |
-| 项目记录 | `记录\记录.md` |
-
----
-
-## 六、项目背景(来自记录.md)
-
-- 战斗校验服使用 `slua-standalone`,Linux 版本未充分测试,目前主要跑 Windows
-- SLua 限制:只能基于 .NET Framework 3.5 及以上开发
-- 逻辑服 HTTP 服务基于 .NET Core 5.0,无法启动 SLua 虚拟机
-- 因此战斗服与游戏逻辑服分离,无法整合在一起

+ 0 - 302
docs/文档6.10/战斗错误6.10.md

@@ -1,302 +0,0 @@
-# chuanzhanServer 对话记录
-
-**日期:** 2026-06-10  
-**项目:** chuanzhanServer  
-**整理内容:** 战斗逻辑位置与战斗结算超时问题、本地起服 127.0.0.1 地址无法访问问题
-
----
-
-## 一、战斗逻辑存放位置与战斗结算报错
-
-### 1.1 问题描述
-
-战斗结束后,GameNode 日志出现如下报错:
-
-```
-ERROR session:111115_1@GameNode1 - A Task Timeout Exception!
-DeepFrozen.RPC.Remote.RpcException : A Task Timeout Exception!
-
-ERROR ConnectorService_1 - Request is : TypeCodec : OpenCards.Core.Protocol.Client.ClientFightResultRequest (217603)
-```
-
-日志时间线示例:
-
-```
-00:06:32  ClientEnterGameRequest Reconnect(玩家重连)
-00:06:32  StartLogicService
-00:06:40  ClientFightResultRequest 超时(约 8 秒)
-00:07:33  ClientFightResultRequest 再次超时(客户端重试)
-```
-
----
-
-### 1.2 战斗逻辑存放位置
-
-项目采用 **客户端 Lua 跑战斗 + 服务端 C# 结算/校验** 的架构。
-
-#### 客户端战斗核心(Lua)
-
-**主目录:** `server/src/data/ClientScript/battle/`
-
-| 文件/目录 | 作用 |
-|-----------|------|
-| `battle_main.lua` | 战斗入口,按模式创建不同 Battle 实例 |
-| `battle_core.lua` | 战斗核心循环(tick、update、结算) |
-| `mode/battle_stage.lua` | 主线关卡战斗模式 |
-| `entity/`、`skill/`、`buff/`、`ai/` | 实体、技能、Buff、AI |
-| `server.lua` | 服务端无头战斗入口(供战斗服校验用) |
-
-**客户端控制层:**
-
-- `gameControl/battle/battleCtrl.lua` — 战斗流程控制
-- `gameControl/battleResult/battleResultCtrl.lua` — 战斗结束后发起 `ClientFightResultRequest`
-
-#### 服务端战斗结算(C#)
-
-| 文件 | 作用 |
-|------|------|
-| `OpenCards.Server.Logic/Module/FightModules.cs` | 战斗结果处理核心:`HandleClientFightResultRequest` |
-| `OpenCards.Core/Protocol/Client/0x35200.Logic.Fight.cs` | 协议定义 `ClientFightResultRequest` |
-
-#### 独立战斗校验服
-
-| 位置 | 作用 |
-|------|------|
-| `server_battle/GameLogic/BattleServer.cs` | HTTP 战斗服,监听 `/fight/reqstartbattle` |
-| `OpenCards.Server.Battle/BattleServer.cs` | 集成版战斗服(当前 Lua 调用被注释) |
-
-战斗校验 HTTP 地址:
-
-```csharp
-public static string FightPostURL = "http://127.0.0.1:8088/fight/reqstartbattle";
-```
-
----
-
-### 1.3 报错原因分析
-
-报错性质是 **RPC 超时**,不是战斗 Lua 逻辑本身抛错。
-
-**调用链:**
-
-```
-客户端 → ConnectorService → SessionService → LogicService(FightModule) → 返回响应
-```
-
-**`ClientFightResultRequest` 服务端处理流程(`FightModules.HandleClientFightResultRequest`):**
-
-1. 校验 `FightMod / StageId / RandomUuid` 是否与开战时一致
-2. (可选)HTTP 调用战斗服重跑校验
-3. 触发 `AsyncEventHandleFightResult` 发奖励、更新关卡
-4. 同步 Public / Center / Bill 等服务
-
-#### 最可能的原因
-
-**① 重连与战斗结算并发冲突(高度可疑)**
-
-重连时 `LogicService` 处理 `ClientEnterGameRequest`,其中有同步阻塞:
-
-```csharp
-// LogicService.cs 第 726 行
-Provider.Execute(NotifyModulesClientEnterGameMustDataAsync).Wait();
-```
-
-`LogicService` 为单线程消息队列,`ClientFightResultRequest` 会排在重连逻辑之后,超过 RPC 超时阈值即报 `Task Timeout Exception`。
-
-**② 下游 RPC 服务响应慢或不可用**
-
-战斗胜利后的结算链路会调用:
-
-- `StageRankModule.CheckStageRankInfo` → StageRank 服务
-- `SyncStageInfo` → Public / Center 服务
-- `HandleSvaeBillInfo` → Bill 服务(保存战报)
-
-任一服务卡住都会导致超时。
-
-**③ 主线战斗校验已被关闭**
-
-`FightModules.cs` 中主线 PVE 的 HTTP 战斗校验代码已被注释:
-
-```csharp
-log.Info("==================  关闭战斗校验   ==================");
-// RequestBattleService(battlerinfo, RandomUuid, ResultInfo);
-```
-
-因此超时**不太可能是**战斗服 Lua 跑战斗太慢,更可能是 LogicService 排队或下游 RPC 阻塞。
-
-**④ RandomUuid 不匹配(会快速失败,不会超时)**
-
-若 `RandomUuid` 与开战时不一致,会直接返回 `CODE_CAN_NOT_FIGHT_CHAPTER`,不会等到超时。
-
-**⑤ ArenaPinnacle 警告无关**
-
-日志中 `Region 1 round contestants is not 8, grouping contestants failed!` 是竞技场巅峰赛分组问题,与本次战斗结算无直接关系。
-
----
-
-### 1.4 排查建议
-
-1. 查 GameNode1 完整日志,搜索 `111115` + `HandleClientFightResultRequest` / `Enter Game`,看重连与战斗结算是否重叠。
-2. 检查下游服务是否正常:StageRankService、PublicService、CenterService、BillService。
-3. 确认 RPC 超时配置(`_launch_server.xml` 中 `DefaultTaskExecuteTimeout`,默认 60000ms)。
-4. 客户端侧:战斗结束后若网络断开又重连,可在重连成功后再提交战斗结果。
-5. 检查 `_launch_server.xml` 中 `DefaultTaskExecuteTimeout` 是否被改得过短(如 5000–8000ms)。
-
----
-
-## 二、本地起服后 127.0.0.1 地址无法访问
-
-### 2.1 问题描述
-
-使用 `_launch_server_local.xml` 起服后,配置中的 `127.0.0.1` 相关网址仍无法访问。
-
-**配置文件路径:** `server/src/server/OpenCards.Server.Main/_launch_server_local.xml`
-
----
-
-### 2.2 两类 127.0.0.1 地址(关键)
-
-配置里的地址分两类,很多**不是**游戏服自己提供的页面。
-
-#### 类型 A:静态配置文件 URL(游戏服不会提供)
-
-```xml
-<ServerListUrl>http://127.0.0.1:8000/serverlist.json</ServerListUrl>
-<StopServerDataURL>http://127.0.0.1/cjw_60000_stop_server.json</StopServerDataURL>
-<UpdateServerConfig>http://127.0.0.1/update_server_config.json</UpdateServerConfig>
-<AndroidClientVersionRecordConfig>http://127.0.0.1/android_version_record_file.json</AndroidClientVersionRecordConfig>
-<!-- 以及 ios 相关配置 -->
-```
-
-这些是 **AccountServer 启动后主动去拉取的配置**,需要本地另起静态 HTTP 服务,游戏服本身不托管这些 JSON。
-
-| URL | 需要的本地服务 |
-|-----|----------------|
-| `http://127.0.0.1:8000/serverlist.json` | 8000 端口静态文件服务 |
-| `http://127.0.0.1/xxx.json`(无端口 = 80) | 80 端口静态文件服务 |
-
-仓库里虽有 `apollo_60000/serverlist.json` 等文件,但:
-
-- 文件名与配置不一致(如 `stop_server_list.json` vs `cjw_60000_stop_server.json`)
-- 没有自动部署到 8000/80 端口的机制
-- `serverlist.json` 里的 `address` 多为远程 IP,本地需改成 `127.0.0.1:19821`
-
-#### 类型 B:游戏服自身 HTTP/TCP 服务
-
-| 服务 | 地址 | 说明 |
-|------|------|------|
-| AccountServer | `http://127.0.0.1:18081/account/` | 账号/区服 API |
-| PayServer | `http://127.0.0.1:18082/pay/` | 支付 |
-| AdminService | `http://127.0.0.1:18088/api/` | 管理后台 |
-| Connector | `127.0.0.1:19821` | 客户端 TCP 连接 |
-
-由 `ServiceLauncher`(`OpenCards.Server.Main` 单节点模式)拉起。
-
----
-
-### 2.3 当时环境检查结果
-
-本机端口检查:**只有 Redis 6379 在监听**,18081、18082、18088、19821、8000 均未监听。
-
-说明游戏服 HTTP 未成功启动,或进程已退出;仅“执行了起服命令”不够,需确认进程在跑且端口已绑定。
-
----
-
-### 2.4 常见原因与处理
-
-| 原因 | 说明 | 处理 |
-|------|------|------|
-| 误把配置拉取地址当可访问页面 | `8000/serverlist.json` 需独立静态服 | 在 JSON 目录执行 `python -m http.server 8000` |
-| 本地缺少配置文件 | `cjw_60000_stop_server.json` 等不存在 | 从 `apollo_60000/` 复制重命名,或改 XML URL |
-| serverlist 地址不是本机 | 仓库内多为远程 IP | 改为 `"address": "127.0.0.1:19821"` |
-| Windows URL ACL 未注册 | `http://+:18081` 等需管理员权限 | 以管理员运行,或手动 `netsh http add urlacl` |
-| 依赖未就绪 | Redis、MySQL、战斗服 | Redis 已运行;Pay 需 MySQL;8088 战斗服在 local 配置中被注释 |
-| 启动方式与配置不匹配 | local.xml 仅 GlobalConfig | Main 用 `OpenCards.Server.Main.exe local`;多节点用 DotNetCore + local2 |
-
-**本地 serverlist.json 示例:**
-
-```json
-[
-  {
-    "id": 1,
-    "index": 0,
-    "name": "本地1区",
-    "address": "127.0.0.1:19821",
-    "state": 1,
-    "is_open": true,
-    "capacity": 2000,
-    "serverid": 1,
-    "groupid": 1,
-    "note": "local"
-  }
-]
-```
-
-**Windows URL ACL 手动注册:**
-
-```powershell
-netsh http add urlacl url=http://+:18081/account/ user=Everyone
-netsh http add urlacl url=http://+:18082/pay/ user=Everyone
-netsh http add urlacl url=http://+:18088/api/ user=Everyone
-netsh http add urlacl url=http://+:18089/chat/ user=Everyone
-```
-
-**Main 单节点启动:**
-
-```powershell
-cd server\src\_output.server\net5.0
-OpenCards.Server.Main.exe local
-```
-
-**验证端口:**
-
-```powershell
-netstat -ano | findstr "18081 19821 8000"
-```
-
-**快速改法(不想搭静态服):**
-
-将 `_launch_server_local.xml` 中的 `ServerListUrl` 等改为 OSS 地址(参考 `_launch_server_dev.xml`),本地联调时把 serverlist 中的 `address` 改成本机 IP:19821。
-
----
-
-### 2.5 推荐本地调试步骤
-
-1. **以管理员身份**启动 Redis(已有)和 MySQL(Pay 需要)。
-2. 准备静态 JSON,启动 8000 端口 HTTP 服务。
-3. 确认 `serverlist.json` 中 `address` 为 `127.0.0.1:19821`。
-4. 以管理员身份启动 `OpenCards.Server.Main.exe local`。
-5. `netstat` 确认 18081、19821、8000 端口监听。
-6. 验证:
-   - 静态配置:`http://127.0.0.1:8000/serverlist.json`
-   - 账号服:`http://127.0.0.1:18081/account/getserverlist`
-
----
-
-## 三、关键文件索引
-
-| 主题 | 路径 |
-|------|------|
-| 战斗 Lua 核心 | `server/src/data/ClientScript/battle/` |
-| 战斗结算 C# | `server/src/server/OpenCards.Server.Logic/Module/FightModules.cs` |
-| 战斗协议 | `server/src/core/OpenCards.Core/Protocol/Client/0x35200.Logic.Fight.cs` |
-| 战斗校验服 | `server_battle/GameLogic/BattleServer.cs` |
-| 本地起服配置 | `server/src/server/OpenCards.Server.Main/_launch_server_local.xml` |
-| 完整多节点配置 | `server/src/server/OpenCards.Server.Main/_launch_server_local2.xml` |
-| 单节点启动器 | `server/src/server/OpenCards.Server.Main/ServiceLauncher.cs` |
-| 起服入口 | `server/src/server/OpenCards.Server.Main/Program.cs` |
-| 静态配置样例 | `apollo_60000/serverlist.json` 等 |
-
----
-
-## 四、总结
-
-| 问题 | 结论 |
-|------|------|
-| 战斗逻辑在哪 | 主要在 `server/src/data/ClientScript/battle/`(Lua),结算在 `FightModules.cs` |
-| 战斗结算超时 | RPC 超时;重连阻塞 LogicService 或 StageRank/Center/Bill 下游慢 |
-| 127.0.0.1 无法访问 | 8000/80 为外部静态配置源,需自建 HTTP;18081 等需游戏服成功启动 + 管理员 URL ACL |
-
----
-
-*文档由 Cursor 对话自动整理生成*

+ 0 - 151
docs/需求整理.txt

@@ -1,151 +0,0 @@
-  货币类 vs 代币类 — 核心区别
-
-  ┌───────────────────────┬──────────────────────────────┬───────────────────────────────────────┐
-  │                       │       货币(Currency)       │             代币(Token)             │
-  ├───────────────────────┼──────────────────────────────┼───────────────────────────────────────┤
-  │ 举例                  │ 钻石(1)、金币(5)、友情点(15) │ 迷宫币(9)、血战筹码(12)、悬赏点(35)   │
-  ├───────────────────────┼──────────────────────────────┼───────────────────────────────────────┤
-  │ 存储位置              │ RoleData 字段(专用字段)    │ RoleItemData.Items 背包(通用道具格) │
-  ├───────────────────────┼──────────────────────────────┼───────────────────────────────────────┤
-  │ 不进背包              │ ✅ 加入 ItemsNotInPackage    │ ❌ 不用加                             │
-  ├───────────────────────┼──────────────────────────────┼───────────────────────────────────────┤
-  │ 需要改 CurrencyModule │ ✅ 必须加                    │ ❌ 不需要                             │
-  ├───────────────────────┼──────────────────────────────┼───────────────────────────────────────┤
-  │ 需要改 RoleData       │ ✅ 需要加字段                │ ❌ 不需要                             │
-  ├───────────────────────┼──────────────────────────────┼───────────────────────────────────────┤
-  │ 客户端同步            │ 通过 SyncClientRoleDataType  │ 通过背包增量同步                      │
-  └───────────────────────┴──────────────────────────────┴───────────────────────────────────────┘
-
-  ---
-  一、新增货币类道具(如新货币)
-
-  步骤 1:item.xlsx 新增一行
-
-  Id=36, ItemType=1(Currency), ItemName=XX货币, Stack=0, AutoUse=0
-
-  步骤 2:iteminstance.xlsx 新增捆绑实例
-
-  InstanceId=3600, ItemId=36, ItemType=1
-
-  步骤 3:ItemDefines.cs 新增 ID 常量 + 加入 ItemsNotInPackage
-
-  文件:server/src/server/OpenCards.Server.Logic/Module/Item/ItemDefines.cs
-
-  public static int NewCurrencyItemId = 36; // 新货币
-
-  public static List<int> ItemsNotInPackage = new List<int>(){
-      DiamondItemId, FreeDiamondItemId, CoinItemId, VipExpItemId,
-      FreeVipExpItemId, HeroExpItemId, FriendPointId, RoleExpId,
-      NewCurrencyItemId  // ← 加这里
-  };
-
-  步骤 4:RoleData.cs 新增存储字段
-
-  文件:server/src/core/OpenCards.Core/ORM/RoleData.cs
-
-  [PersistField]
-  [SyncClientData(SyncClientRoleDataType.NewCurrency)]  // 如需客户端实时同步
-  public int NewCurrency;
-
-  步骤 5:0x35300.Logic.Role.cs 新增同步枚举值
-
-  文件:server/src/core/OpenCards.Core/Protocol/Client/0x35300.Logic.Role.cs
-
-  public enum SyncClientRoleDataType
-  {
-      // ... 现有值 ...
-      PresetHeroSelect2 = 45,
-      NewCurrency = 46,  // ← 新增
-  }
-
-  步骤 6:CurrencyModule.cs 新增增删查逻辑
-
-  文件:server/src/server/OpenCards.Server.Logic/Module/CurrencyModule.cs
-
-  在 OnEventAddItemImp 中加:
-  else if (itemInstance.ItemId == ItemDefines.NewCurrencyItemId)
-  {
-      if (ItemDefines.ItemsNotInPackage.Contains(itemInstance.ItemId))
-      {
-          beforeItemJObject.Add(EventTrackPropertyDefines.ResourceID, itemInstance.ItemId);
-          beforeItemJObject.Add(EventTrackPropertyDefines.ResourceName, itemName);
-          beforeItemJObject.Add(EventTrackPropertyDefines.ResourceNum, roleModule.roleDataMapping.NewCurrency);
-          needDig = true;
-      }
-      roleModule.roleDataMapping.NewCurrency += itemInstance.Count;
-  }
-
-  在 OnEventRemoveItem 中加:
-  else if (itemid == ItemDefines.NewCurrencyItemId)
-  {
-      if (ItemDefines.ItemsNotInPackage.Contains(itemid))
-          beforeItemJObject.Add(EventTrackPropertyDefines.ResourceNum, roleDataMapping.NewCurrency);
-      if (roleModule.roleDataMapping.NewCurrency < count)
-          success.value = false;
-      else
-      {
-          roleModule.roleDataMapping.NewCurrency -= count;
-          success.value = true;
-      }
-  }
-
-  在 OnEventGetItemCount 中加:
-  else if (itemid == ItemDefines.NewCurrencyItemId)
-  {
-      count.value = roleDataMapping.NewCurrency;
-  }
-
-  ---
-  二、新增代币类道具(不进背包,但走通用背包存储)
-
-  代币比货币简单得多,只需要配置表 + 常量,不需要改 CurrencyModule 和 RoleData。
-
-  步骤 1:item.xlsx 新增一行
-
-  Id=37, ItemType=3(Token), ItemName=XX代币, Stack=999999, AutoUse=0
-
-  步骤 2:iteminstance.xlsx 新增捆绑实例
-
-  InstanceId=3700, ItemId=37, ItemType=1
-
-  步骤 3:ItemDefines.cs 新增 ID 常量(不加 ItemsNotInPackage)
-
-  public static int NewTokenItemId = 37; // 新代币(走背包存储)
-
-  步骤 4:业务代码中使用
-
-  发放:
-  DispatchEvent(EventDefines.EventAddItem,
-      新的ItemInstanceId,  // iteminstance.xlsx 中的 InstanceId
-      count,
-      AddItemReason.YourReason, 0, LogicUtils.FileLine);
-
-  查询数量:
-  Ref<long> count = new Ref<long>();
-  DispatchEvent(EventDefines.EventGetItemCount, ItemDefines.NewTokenItemId, count);
-
-  扣除:
-  Ref<bool> success = new Ref<bool>();
-  DispatchEvent(EventDefines.EventRemoveItem, ItemDefines.NewTokenItemId,
-      amount, RemoveItemReason.YourReason, 0, LogicUtils.FileLine, success);
-
-  ---
-  总结对比
-
-  ┌────────────────────────┬───────────────┬───────────┐
-  │         修改点         │    货币类     │  代币类   │
-  ├────────────────────────┼───────────────┼───────────┤
-  │ item.xlsx              │ ✅            │ ✅        │
-  ├────────────────────────┼───────────────┼───────────┤
-  │ iteminstance.xlsx      │ ✅            │ ✅        │
-  ├────────────────────────┼───────────────┼───────────┤
-  │ ItemDefines.cs 常量    │ ✅            │ ✅        │
-  ├────────────────────────┼───────────────┼───────────┤
-  │ ItemsNotInPackage      │ ✅ 必须加     │ ❌ 不加   │
-  ├────────────────────────┼───────────────┼───────────┤
-  │ RoleData.cs 新字段     │ ✅ 必须加     │ ❌ 不需要 │
-  ├────────────────────────┼───────────────┼───────────┤
-  │ SyncClientRoleDataType │ ✅ 新增枚举   │ ❌ 不需要 │
-  ├────────────────────────┼───────────────┼───────────┤
-  │ CurrencyModule.cs      │ ✅ 三处加逻辑 │ ❌ 不需要 │
-  └────────────────────────┴───────────────┴───────────┘

+ 0 - 1
local_static/pids/http_80.pid

@@ -1 +0,0 @@
-35848

+ 0 - 1
local_static/pids/http_8000.pid

@@ -1 +0,0 @@
-20884

+ 0 - 1
local_static/port80/android_version_record_file.json

@@ -1 +0,0 @@
-[]

+ 0 - 1
local_static/port80/android_zip_record_file.json

@@ -1 +0,0 @@
-[]

+ 0 - 10
local_static/port80/cjw_60000_stop_server.json

@@ -1,10 +0,0 @@
-[
-  {
-    "id": 1081,
-    "server_id": 2,
-    "state": 2,
-    "start_time": 1662804900,
-    "stop_time": 1662806700,
-    "advance_send_email_time": 3
-  }
-]

+ 0 - 1
local_static/port80/ios_version_record_file.json

@@ -1 +0,0 @@
-[]

+ 0 - 1
local_static/port80/ios_zip_record_file.json

@@ -1 +0,0 @@
-[]

+ 0 - 26
local_static/port80/serverlist.json

@@ -1,26 +0,0 @@
-[
-  {
-    "id": 1,
-    "index": 0,
-    "name": "fxd测试",
-    "address": "192.168.0.85:19821",
-    "state": 1,
-    "is_open": true,
-    "capacity": 2000,
-    "serverid": 1,
-    "groupid": 1,
-    "note": "local"
-  },
-  {
-    "id": 2,
-    "index": 1,
-    "name": "测试服",
-    "address": "43.226.57.217:19821",
-    "state": 1,
-    "is_open": true,
-    "capacity": 2000,
-    "serverid": 2,
-    "groupid": 2,
-    "note": "测试服"
-  }
-]

+ 0 - 12
local_static/port80/update_server_config.json

@@ -1,12 +0,0 @@
-[
-  {
-    "productLine": "dev",
-    "deviceType": "android",
-    "targetAppVersion": "0.0.0",
-    "forceUpdateUrl": "https://www.baidu.com/sov_{0}_{1}.apk",
-    "resServerUrlPrefix": "http://127.0.0.1/apollo_60000",
-    "loginServerUrl": "http://127.0.0.1:18081/",
-    "appVersionReview": "0.0.0",
-    "loginServerUrlReview": "http://127.0.0.1:18081/"
-  }
-]

+ 0 - 14
local_static/port8000/serverlist.json

@@ -1,14 +0,0 @@
-[
-  {
-    "id": 1,
-    "index": 0,
-    "name": "fxd测试",
-    "address": "127.0.0.1:19821",
-    "state": 1,
-    "is_open": true,
-    "capacity": 2000,
-    "serverid": 1,
-    "groupid": 1,
-    "note": "local"
-  }
-]