免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12345下一页
最近访问板块 发新帖
查看: 34124 | 回复: 40
打印 上一主题 下一主题

【POE 应用程序实例】决战脱机外挂 [复制链接]

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-03-13 18:22 |只看该作者 |倒序浏览
运行效果:
  1. D:\MoChou\droiyan>client.pl
  2. 决战文本客户端 0.91 版第 78 次修订版。

  3. 正在加载物品信息表……
  4. 加载完成。
  5. 03-13 18:17:34 连接成功: Session Manager Server 连接成功。
  6. 03-13 18:17:34 正在登录: 已发送登录请求。
  7. 03-13 18:17:34 正在登录: 接收到登录响应报文。
  8. 03-13 18:17:34 登录成功: 登录成功。
  9. 03-13 18:17:34 开始游戏: 已发送开始游戏请求。
  10. 03-13 18:17:34 开始游戏: 接收到连接信息报文。
  11. 03-13 18:17:34 开始游戏: 用户名: [flw] 服务器个数: [1]
  12. 03-13 18:17:34 开始游戏: IP: [flw masked] Port: [9000]
  13. 03-13 18:17:34 正在连接: 正在连接 CharInfoServer ……
  14. 03-13 18:17:34 连接成功: CharInfoServer 连接成功。
  15. 03-13 18:17:34 请求公钥: 正在请求加密公钥……
  16. 03-13 18:17:35 请求公钥: 接收到公钥响应报文。
  17. 03-13 18:17:35 请求公钥: 公钥: a1a5e326d7297da9
  18. 03-13 18:17:35 得到公钥: 已经获得加密公钥。
  19. 03-13 18:17:35 登录游戏: 已发送登录游戏请求。
  20. 03-13 18:17:35 登录游戏: 接收到加密报文,自动解密……
  21. 03-13 18:17:35 登录游戏: 报文已解密。包序号:[1]。
  22. 03-13 18:17:35 登录游戏: 接收到帐户登录结果报文。
  23. 03-13 18:17:35 登录成功: 登录成功。角色信息:
  24. ===============================================================================
  25. 角色: [flw1] 性别: [男] 职业: [枪手] 级别: [104]
  26. 力量: [47] 体质: [48] 敏捷: [49] 智慧: [50] 智力: [51]
  27. 生命: [312/536] 精神: [584/584] 体力: [224/224]
  28. ===============================================================================
  29. 03-13 18:17:35 断开连接: 已发送断开角色服务器请求。
  30. 03-13 18:17:35 断开连接: 接收到加密报文,自动解密……
  31. 03-13 18:17:35 断开连接: 报文已解密。包序号:[2]。
  32. 03-13 18:17:35 断开连接: 接收到断开角色服务器响应报文。
  33. 03-13 18:17:35 断开连接: 断开角色服务器
  34. 03-13 18:17:35 进入游戏: 正在连接 ZoneServer ……
  35. 03-13 18:17:35 连接成功: ZoneServer 连接成功。
  36. 03-13 18:17:35 请求公钥: 正在请求加密公钥……
  37. 03-13 18:17:35 请求公钥: 接收到公钥响应报文。
  38. 03-13 18:17:35 请求公钥: 公钥: a1a5e326d7297da9
  39. 03-13 18:17:35 得到公钥: 已经获得加密公钥。
  40. 03-13 18:17:35 进入游戏: 已发送进入游戏请求。
  41. 03-13 18:17:35 进入游戏: 接收到加密报文,自动解密……
  42. 03-13 18:17:35 进入游戏: 报文已解密。包序号:[1]。
  43. 03-13 18:17:35 进入游戏: 接收到压缩报文,自动解压……
  44. 03-13 18:17:35 进入游戏: 报文已解压。长度: [1036]。
  45. 03-13 18:17:35 进入游戏: 接收到人物信息通知报文。
  46. 03-13 18:17:35 进入游戏: 接收到人物 flw1 的详细信息。
  47. 03-13 18:17:35 进入游戏: 接收到加密报文,自动解密……
  48. 03-13 18:17:35 进入游戏: 报文已解密。包序号:[2]。
  49. 03-13 18:17:35 进入游戏: 接收到进入游戏响应报文。
  50. 03-13 18:17:35 进入游戏: 断开 Session Manager Server。
  51. 03-13 18:17:35 普通在线: 已进入游戏。
  52. 03-13 18:17:35 普通在线: 位置:[390, 1328]; 面向:[7]; 游戏时间:[8 点 10 分]
  53. 03-13 18:17:35 普通在线: 服用药品 生命回复剂A
  54. 03-13 18:17:35 普通在线: 8 点了。
  55. 03-13 18:17:35 普通在线: 接收到人物 flw1 的概要信息。
  56. 03-13 18:17:35 普通在线: 发现了一个怪物,名称 [低等异形] HP [65] 位置 [368,1312]
  57. 03-13 18:17:35 普通在线: 有 1 个怪物信息。
  58. 03-13 18:17:35 普通在线: 接收到一般消息: [欢迎来到游戏天地]
  59. 03-13 18:17:35 普通在线: 接收到一般消息: [http://url/flw/masked]
  60. 03-13 18:17:35 正在移动: 跑动 cur: 390,1328 next1: 389,1327 next2: 387,1327
  61. 03-13 18:17:36 正在移动: 物品使用结果: 生命恢复到 372。
  62. 03-13 18:17:36 正在移动: flw1 吃下了 生命恢复剂
  63. 03-13 18:17:36 正在移动: 10007 经由 389, 1327 跑到 387, 1327
  64. 03-13 18:17:36 正在移动: 跑动 cur: 387,1327 next1: 386,1326 next2: 385,1325
  65. 03-13 18:17:36 正在移动: 体力减少了 1,目前是 223/224
  66. 03-13 18:17:36 正在移动: 服用药品 生命回复剂A
  67. 03-13 18:17:37 正在移动: 10007 经由 386, 1326 跑到 385, 1325
  68. 03-13 18:17:37 正在移动: 跑动 cur: 385,1325 next1: 384,1324 next2: 383,1323
  69. 03-13 18:17:37 正在移动: 体力减少了 1,目前是 222/224
  70. 03-13 18:17:37 正在移动: 服用药品 生命回复剂A
  71. 03-13 18:17:37 正在移动: 物品使用结果: 生命恢复到 432。
  72. 03-13 18:17:37 正在移动: flw1 吃下了 生命恢复剂
  73. 03-13 18:17:38 正在移动: 10007 经由 384, 1324 跑到 383, 1323
  74. 03-13 18:17:38 正在移动: 跑动 cur: 383,1323 next1: 382,1322 next2: 381,1321
  75. 03-13 18:17:38 正在移动: 体力减少了 1,目前是 221/224
  76. 03-13 18:17:38 正在移动: 服用药品 生命回复剂A
  77. 03-13 18:17:38 正在移动: 物品使用结果: 生命恢复到 492。
  78. 03-13 18:17:38 正在移动: flw1 吃下了 生命恢复剂
  79. 03-13 18:17:39 正在移动: 10007 经由 382, 1322 跑到 381, 1321
  80. 03-13 18:17:40 正在移动: 跑动 cur: 381,1321 next1: 379,1321 next2: 378,1320
  81. 03-13 18:17:40 正在移动: 20752 嗖~地一声消失了……
  82. 03-13 18:17:40 正在移动: 体力减少了 1,目前是 220/224
  83. 03-13 18:17:40 正在移动: 服用药品 生命回复剂A
  84. 03-13 18:17:40 正在移动: 物品使用结果: 生命恢复到 552。
  85. 03-13 18:17:40 正在移动: flw1 吃下了 生命恢复剂
  86. 03-13 18:17:40 正在移动: 10007 经由 379, 1321 跑到 378, 1320
  87. 03-13 18:17:40 正在移动: 跑动 cur: 378,1320 next1: 377,1319 next2: 376,1318
  88. 03-13 18:17:40 正在移动: 体力减少了 1,目前是 219/224
  89. 03-13 18:17:40 正在移动: 服用药品 生命回复剂A
  90. 03-13 18:17:41 正在移动: 物品使用结果: 生命恢复到 612。
  91. 03-13 18:17:41 正在移动: flw1 吃下了 生命恢复剂
  92. 03-13 18:17:41 正在移动: 10007 经由 377, 1319 跑到 376, 1318
  93. 03-13 18:17:41 正在移动: 跑动 cur: 376,1318 next1: 375,1317 next2: 374,1316
  94. 03-13 18:17:41 正在移动: 体力减少了 1,目前是 218/224
  95. 03-13 18:17:41 正在移动: 服用药品 生命回复剂A
  96. 03-13 18:17:41 正在移动: 物品使用结果: 生命恢复到 626。
  97. 03-13 18:17:41 正在移动: flw1 吃下了 生命恢复剂
  98. 03-13 18:17:42 正在移动: 10007 经由 375, 1317 跑到 374, 1316
  99. 03-13 18:17:42 正在移动: 跑动 cur: 374,1316 next1: 372,1316 next2: 371,1315
  100. 03-13 18:17:42 正在移动: 体力减少了 1,目前是 217/224
  101. 03-13 18:17:42 正在移动: 服用药品 生命回复剂A
  102. 03-13 18:17:42 正在移动: 物品使用结果: 生命恢复到 626。
  103. 03-13 18:17:42 正在移动: flw1 吃下了 生命恢复剂
  104. 03-13 18:17:43 正在移动: 10007 经由 372, 1316 跑到 371, 1315
  105. 03-13 18:17:43 正在移动: 跑动 cur: 371,1315 next1: 370,1314 next2: 369,1313
  106. 03-13 18:17:43 正在移动: 体力减少了 1,目前是 216/224
  107. 03-13 18:17:43 正在移动: 服用药品 生命回复剂A
  108. 03-13 18:17:43 正在移动: 物品使用结果: 生命恢复到 626。
  109. 03-13 18:17:43 正在移动: flw1 吃下了 生命恢复剂
  110. 03-13 18:17:44 正在移动: 10007 经由 370, 1314 跑到 369, 1313
  111. 03-13 18:17:44 普通在线: 体力减少了 1,目前是 215/224
  112. 03-13 18:17:44 普通在线: 服用药品 生命回复剂A
  113. 03-13 18:17:44 普通在线: 打 20644
  114. 03-13 18:17:44 普通在线: 攻击 20644
  115. 03-13 18:17:44 普通在线: 物品使用结果: 生命恢复到 626。
  116. 03-13 18:17:44 普通在线: flw1 吃下了 生命恢复剂
  117. 03-13 18:17:45 普通在线: 物品使用结果: 生命恢复到 626。
  118. 03-13 18:17:45 普通在线: flw1 吃下了 生命恢复剂
  119. 03-13 18:17:45 普通在线: 你想打 20644,结果它灵巧地躲开了
  120. 03-13 18:17:45 普通在线: 打 20810
  121. 03-13 18:17:45 普通在线: 攻击 20810
  122. 03-13 18:17:46 普通在线: 20810 想打你,结果你灵巧地躲开了
  123. 03-13 18:17:46 普通在线: 你打中了 20810, 它的 HP: [48/70]
  124. 03-13 18:17:46 普通在线: 攻击 20644
  125. 03-13 18:17:47 普通在线: 生命值增加了 307,目前是 619/626
  126. 03-13 18:17:47 普通在线: 20644 打中了你, 你的 HP: [619/626]
  127. 03-13 18:17:47 普通在线: 你打中了 20644, 它的 HP: [44/65]
  128. 03-13 18:17:47 普通在线: 20810 想打你,结果你灵巧地躲开了
  129. 03-13 18:17:47 普通在线: 攻击 20810
  130. 03-13 18:17:48 普通在线: 你想打 20810,结果它灵巧地躲开了
  131. 03-13 18:17:48 普通在线: 打 20769
  132. 03-13 18:17:48 普通在线: 攻击 20769
  133. 03-13 18:17:48 普通在线: 生命值减少了 7,目前是 612/626
  134. 03-13 18:17:48 普通在线: 20644 打中了你, 你的 HP: [612/626]
  135. 03-13 18:17:48 普通在线: 20810 想打你,结果你灵巧地躲开了
  136. 03-13 18:17:48 普通在线: 攻击 20644
  137. 03-13 18:17:49 普通在线: 生命值减少了 6,目前是 606/626
  138. 03-13 18:17:49 普通在线: 20769 打中了你, 你的 HP: [606/626]
  139. 03-13 18:17:49 普通在线: 你打中了 20769, 它的 HP: [47/70]
  140. Terminating on signal SIGINT(2)
复制代码

[ 本帖最后由 flw 于 2007-12-26 18:12 编辑 ]

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
2 [报告]
发表于 2007-03-13 18:23 |只看该作者
主程序:
  1. use strict;
  2. use warnings;

  3. # 加载 POE 以及本程序中所使用到的组件
  4. # 之所以要写这么多是因为有些动态加载的模块 PerlAPP 识别不到,
  5. # 编译后缺少东西不能正常运行,所以必须得写明了。
  6. use POE;
  7. use POE::Session;
  8. use POE::Loop::Select;
  9. use POE::Wheel::SocketFactory;
  10. use POE::Wheel::ReadWrite;
  11. use POE::Driver::SysRW;
  12. use POE::Filter::Line;
  13. use POE::Filter::Stream;
  14. use POE::Resource::Aliases;
  15. use POE::Resource::Events;
  16. use POE::Resource::Extrefs;
  17. use POE::Resource::FileHandles;
  18. use POE::Resource::SIDs;
  19. use POE::Resource::Sessions;
  20. use POE::Resource::Signals;
  21. use POE::Resource::Statistics;
  22. use POE::Resource::Controls;
  23. use POE::Wheel;

  24. use JzCrypt;
  25. use GameClient;

  26. use YAML qw(LoadFile DumpFile);

  27. print "决战文本客户端 0.91 版第 $GameClient::VERSION 次修订版。\n\n";

  28. print "正在加载物品信息表……\n";
  29. $GameClient::ddItemInfo = LoadFile( 'items.yaml' );
  30. #use Data::Dumper;
  31. #print Dumper( $GameClient::ddItemInfo );
  32. print "加载完成。\n";
  33. #die;

  34. $GameClient::Debug = 1;

  35. my $session = POE::Session->create(
  36.     inline_states => {
  37.         _start                  => \&game_start,
  38.         _stop                   => \&game_stop,
  39.         sm_connected            => \&sm_connected,
  40.         sm_input                => \&sm_input,
  41.         charinfo_connected      => \&charinfo_connected,
  42.         charinfo_input          => \&charinfo_input,
  43.         zoneserver_connected    => \&zoneserver_connected,
  44.         zoneserver_input        => \&zoneserver_input,
  45.         sock_error              => \&sock_error,
  46. #        out_flushed     => \&server_flush,
  47.     },
  48.     args => [
  49.         @ARGV
  50.     ],
  51. );

  52. our $Game = new GameClient( $session );

  53. POE::Kernel->run();

  54. sub game_start {
  55.     my ($kernel, $heap, $user, $pass, $ip, $port ) = @_[KERNEL, HEAP, ARG0, ARG1, ARG2, ARG3];

  56.     $heap->{user} = $user || 'flw';
  57.     $heap->{pass} = $pass || 'admin';
  58.     $ip = 'flw.masked' unless $ip;
  59.     $port = 23 unless $port;

  60.     $heap->{sm_socket} = POE::Wheel::SocketFactory->new(
  61.         RemoteAddress  => $ip,
  62.         RemotePort     => $port,
  63.         SuccessEvent   => 'sm_connected',
  64.         FailureEvent   => 'sock_error',
  65.     );
  66. }

  67. sub game_stop {
  68.     $Game->logmsg( "游戏退出。" );
  69. }

  70. sub sm_connected {
  71.     my ($kernel, $session, $heap, $commSocket) = @_[KERNEL, SESSION, HEAP, ARG0];

  72.     $heap->{sm_wheel} = new POE::Wheel::ReadWrite(
  73.         Handle       => $commSocket,                # 将已经 connect 成功的 socket 句柄链接到这个轮子上
  74.         Driver       => new POE::Driver::SysRW,     # 使用 sysread/syswrite 来驱动这个轮子
  75.         InputFilter  => new POE::Filter::Stream(),
  76.         OutputFilter => new POE::Filter::Stream,
  77.         InputEvent   => 'sm_input',
  78.         ErrorEvent   => 'sock_error',               # 有错误发生时,产生一个 ``io_error'' 事件。
  79. #        FlushedEvent => 'out_flushed',              # 输出被 flushed 时的事件,不需要处理。
  80.     );  

  81.     $Game->{sm_wheel} = $heap->{sm_wheel};
  82.     $Game->{state} = '连接成功';
  83.     $Game->logmsg( 'Session Manager Server 连接成功。' );
  84.     $Game->Login( $heap->{user}, $heap->{pass} );
  85. }

  86. sub sm_input {
  87.     my ($kernel, $heap, $chunk) = @_[KERNEL, HEAP, ARG0];

  88.     my ($name, $id, $data) = $Game->SMPacketType( $chunk );
  89.     if ( defined $name ){
  90.         $Game->logmsg( "接收到$name报文。" );
  91.         $Game->SMParse( $id, $data );
  92.     }
  93.     else{
  94.         $Game->logmsg( sprintf "接收到未知 [0x%02X:%d] 报文。", $id, $id - 0x8000 );
  95.         $Game->pkgDump( $data, 'From SM:' );
  96.     }
  97. }

  98. sub charinfo_connected {
  99.     my ($kernel, $session, $heap, $commSocket) = @_[KERNEL, SESSION, HEAP, ARG0];

  100.     $heap->{wheel} = new POE::Wheel::ReadWrite(
  101.         Handle       => $commSocket,                # 将已经 connect 成功的 socket 句柄链接到这个轮子上
  102.         Driver       => new POE::Driver::SysRW,     # 使用 sysread/syswrite 来驱动这个轮子
  103.         InputFilter  => new POE::Filter::Stream(),
  104.         OutputFilter => new POE::Filter::Stream,
  105.         InputEvent   => 'charinfo_input',
  106.         ErrorEvent   => 'sock_error',               # 有错误发生时,产生一个 ``io_error'' 事件。
  107. #        FlushedEvent => 'out_flushed',              # 输出被 flushed 时的事件,不需要处理。
  108.     );  

  109.     $Game->{wheel} = $heap->{wheel};
  110.     $Game->{state} = '连接成功';
  111.     $Game->logmsg( 'CharInfoServer 连接成功。' );
  112.     $Game->GetPublicKey(0);
  113. }

  114. sub charinfo_input {
  115.     my ($kernel, $heap, $chunk) = @_[KERNEL, HEAP, ARG0];

  116.     if ( substr( $chunk, 0, 2 ) ne "\xaa\x55"
  117.         or substr( $chunk, -2, 2 ) ne "\x55\xaa" ){
  118.         $Game->logmsg( '报文格式有误。' );
  119.     }

  120.     $chunk = unpack( 's/a*', substr( $chunk, 2, -2 ) );
  121.     my ($name, $id, $data) = $Game->CIPacketType( $chunk );
  122.     if ( defined $name ){
  123.         $Game->logmsg( "接收到$name报文。" );
  124.         $Game->CIParse( $id, $data );
  125.     }
  126.     else{
  127.         $Game->logmsg( sprintf( "接收到未知 [%x] 报文。", $id ) );
  128.         $Game->pkgDump( $data, 'From CI:' );
  129.     }
  130. }

  131. sub zoneserver_connected {
  132.     my ($kernel, $session, $heap, $commSocket) = @_[KERNEL, SESSION, HEAP, ARG0];

  133.     $heap->{wheel} = new POE::Wheel::ReadWrite(
  134.         Handle       => $commSocket,                # 将已经 connect 成功的 socket 句柄链接到这个轮子上
  135.         Driver       => new POE::Driver::SysRW,     # 使用 sysread/syswrite 来驱动这个轮子
  136.         InputFilter  => new POE::Filter::Line( Literal => "\x55\xAA" ),
  137.         OutputFilter => new POE::Filter::Stream,
  138.         InputEvent   => 'zoneserver_input',
  139.         ErrorEvent   => 'sock_error',               # 有错误发生时,产生一个 ``io_error'' 事件。
  140. #        FlushedEvent => 'out_flushed',              # 输出被 flushed 时的事件,不需要处理。
  141.     );  

  142.     $Game->{wheel} = $heap->{wheel};
  143.     $Game->{state} = '连接成功';
  144.     $Game->logmsg( 'ZoneServer 连接成功。' );

  145.     $Game->GetPublicKey(0);
  146. }

  147. sub zoneserver_input {
  148.     my ($kernel, $heap, $chunk) = @_[KERNEL, HEAP, ARG0];

  149.     if ( substr( $chunk, 0, 2 ) ne "\xaa\x55" ){
  150.         # 报文的结束部分 0x55AA 已经被 POE:F:Line 过滤掉了。
  151.         $Game->logmsg( '报文格式有误。' );
  152.         return;
  153.     }

  154.     $chunk = unpack( 's/a*', substr( $chunk, 2 ) );
  155.     my ($name, $id, $data) = $Game->CIPacketType( $chunk );
  156.     if ( defined $name ){
  157.         unless ( $Game->{online} ){ # 玩家在线后就不再提示此类信息。
  158.             $Game->logmsg( "接收到$name报文。" );
  159.         }
  160.         $Game->CIParse( $id, $data );
  161.     }
  162.     else{
  163.         $Game->todo( $id, $data );
  164.         $Game->logmsg( sprintf( "接收到未知 [0x%02X:%d] 报文。", $id, $id ) );
  165.         $Game->logmsg( "请将当前目录的 todo.txt 文件送交开发人员,谢谢合作!" );
  166.     }
  167. }

  168. sub sock_error {
  169.         my ($kernel, $session, $operation, $errnum, $errstr) =
  170.                 @_[KERNEL, SESSION, ARG0, ARG1, ARG2];

  171.     {
  172.         local $^E = $errnum;  # $^E 比 $! 的信息更加详细,而且还是本地语言的。
  173.         $errstr = $^E;
  174.     }

  175.     $Game->logmsg( "通讯错误:[$errnum]", $errstr );
  176. }
复制代码

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
3 [报告]
发表于 2007-03-13 18:24 |只看该作者
主要逻辑处理:也就是 GameClient.pm
  1. package GameClient;

  2. our ($VERSION) = q$Revision: 78 $ =~ /(\d+)/;

  3. use strict;
  4. use warnings;
  5. use JzCrypt;
  6. use JzCompress;
  7. use Data::HexDump;
  8. use Data::Dumper;
  9. use POE;
  10. use Time::HiRes qw(usleep);

  11. our @ddClassCode = qw(格斗家 魔法师 剑士 枪手);
  12. our @ddGenderCode = qw(男 女);
  13. our @ddNpcTypeCode = qw(怪物 NPC 门 守卫 移动守卫 工会NPC 工会守卫 工会特殊 工会门);
  14. our @ddSystemMsgTypeCode = qw(特殊 一般 错误 NPC 通知 断线);
  15. our @ddChatTypeCode = qw(普通聊天 悄悄话 喊叫 军团聊天 好友聊天 好友邀请 好友分手);

  16. our $ddItemInfo = {};

  17. our $Debug = 1;

  18. use constant SM_BASE => 0x8000;
  19. use constant SM_LOGIN_REQ => SM_BASE + 0;
  20. use constant SM_LOGIN_ACK => SM_BASE + 1;

  21. use constant SM_GAME_REQ => SM_BASE + 12;
  22. use constant SM_GAME_ACK => SM_BASE + 13;

  23. use constant SM_CONNECTINFO_ACK => SM_BASE + 100;

  24. use constant SM_GAMESERVER_CONNECT_REQ => SM_BASE + 502; # 81F6
  25. use constant SM_GAMESERVER_CONNECT_ACK => SM_BASE + 503; # 81F7

  26. use constant CI_ACCOUNT_LOGIN_REQ => 0x0C;
  27. use constant CI_ACCOUNT_LOGIN_ACK => 0x0D;

  28. use constant CI_DISCONNECT_REQ => 0xEB;
  29. use constant CI_DISCONNECT_ACK => 0xEB; # 注意这里响应和请求是同一个编码

  30. use constant CI_PUBLIC_KEY_REQ => 0xD5;
  31. use constant CI_PUBLIC_KEY_ACK => 0xD6;

  32. use constant CI_CRYPT_PACKET => 0xF9;
  33. use constant ZS_COMPRESS_PACKET => 0xC1;

  34. use constant ZS_USER_INFO => 1;
  35. use constant ZS_CHAR_INFO => 2;
  36. use constant ZS_NPC_INFO => 3;
  37. use constant ZS_SYSTEM_MSG => 5;
  38. use constant ZS_WEATHER_CHANGE => 7;
  39. use constant ZS_FIELD_ITEM_INFO => 58;
  40. use constant ZS_USER_STATUS_UPDATE => 79;

  41. use constant ZS_GAME_START_REQ => 14;
  42. use constant ZS_GAME_START_ACK => 15;

  43. use constant ZS_MOVE_FIRST_ACK => 91;
  44. use constant ZS_MOVE_REQ => 92;
  45. use constant ZS_MOVE_ACK => 93;
  46. use constant ZS_MOVE_END_ACK => 95;

  47. use constant ZS_SET_EXP => 76;
  48. use constant ZS_SET_SP => 102;
  49. use constant ZS_SET_HP => 107;

  50. use constant ZS_RUN_FIRST_REQ => 96;
  51. use constant ZS_RUN_FIRST_ACK => 97;

  52. use constant ZS_RUN_REQ => 98;
  53. use constant ZS_RUN_ACK => 99;

  54. use constant ZS_CHAT_RESULT => 31;

  55. use constant ZS_ATTACK_REQ => 111;
  56. use constant ZS_ATTACK_ACK => 112;
  57. use constant ZS_DEAD_ACK => 113;

  58. use constant ZS_ITEM_USE_REQ => 55;
  59. use constant ZS_ITEM_USE_ACK => 56;
  60. use constant ZS_USE_POSTION_NOTICE => 119;

  61. use constant MAC => '00.13.CE.1B.0C.11';

  62. our %gSMPacketID = (
  63.     SM_LOGIN_ACK() => [ '登录响应', \&LoginAck ],
  64.     SM_CONNECTINFO_ACK() => [ '连接信息', \&ConnectInfoAck ],
  65.     SM_GAMESERVER_CONNECT_ACK() => [ '连接游戏应答', \&GameServerConnectAck ],
  66. );

  67. our %gCIPacketID = (
  68.     CI_PUBLIC_KEY_REQ()         => [ '请求公钥', undef ],
  69.     CI_PUBLIC_KEY_ACK()         => [ '公钥响应', \&PublicKeyAck ],
  70.     CI_ACCOUNT_LOGIN_ACK()      => [ '帐户登录结果', \&CIAccountLoginAck ],
  71.     CI_DISCONNECT_ACK()         => [ '断开角色服务器响应', \&CIServerDisconnectAck ],
  72.     ZS_GAME_START_ACK()         => [ '进入游戏响应', \&GameStartAck ],
  73.     ZS_MOVE_FIRST_ACK()         => [ ' NPC 或玩家开始移动信息', \&MoveAck ],
  74.     ZS_MOVE_ACK()               => [ ' NPC 或玩家移动信息', \&MoveAck ],
  75.     ZS_MOVE_END_ACK()           => [ ' NPC 或玩家终止移动信息', \&MoveAck ],
  76.     ZS_SYSTEM_MSG()             => [ '系统消息', \&SystemMsg ],
  77.     ZS_WEATHER_CHANGE()         => [ '天气变化', \&WeatherChange ],
  78.     ZS_USER_STATUS_UPDATE()     => [ '玩家状态变化信息', \&UserStatusUpdate ],

  79.     ZS_USER_INFO()              => [ '玩家信息通知', \&UserInfo ],
  80.     ZS_CHAR_INFO()              => [ '人物信息通知', \&CharInfo ],
  81.     ZS_NPC_INFO()               => [ ' NPC 信息通知信息', \&NpcInfo ],

  82.     ZS_ATTACK_ACK()             => [ '攻击响应', \&AttackAck ],
  83.     ZS_DEAD_ACK()               => [ '死亡通知', \&DeadAck ],
  84.    
  85.     ZS_SET_HP()                 => [ '生命变化通知', \&SetHP ],
  86.     ZS_SET_EXP()                => [ '经验值变化通知', \&SetExp ],
  87.     ZS_SET_SP()                 => [ '体力变化通知', \&SetSP ],
  88.     ZS_FIELD_ITEM_INFO()        => [ '地图物品信息通知', \&FieldItemInfo ],

  89.     ZS_ITEM_USE_ACK()           => [ '使用物品响应', \&ItemUseAck ],
  90.     ZS_USE_POSTION_NOTICE()     => [ '服用药物通知', \&UsePotionNotice ],

  91.     ZS_RUN_FIRST_ACK()          => [ '开始跑动响应', \&RunAck ],
  92.     ZS_RUN_ACK()                => [ '跑动响应', \&RunAck ],

  93.     ZS_CHAT_RESULT()            => [ '聊天消息', \&ChatResult ],
  94. );

  95. our $Kernel;

  96. sub new {
  97.     my $class = shift;
  98.     $class = ref $class || $class;
  99.     my $session = shift;
  100.     my $itemDB = shift;
  101.     if ( ref $session ne 'POE::Session' ){
  102.         return undef;
  103.     }

  104.     *Kernel = \$main::poe_kernel;
  105.     return bless {
  106.         session     => $session,
  107.         heap        => $session->get_heap(),
  108.         state       => '游戏启动',
  109.         main_state  => '游戏启动',
  110.         online       => 0,
  111.         send_req    => 1,
  112.     }, $class;
  113. }

  114. sub SMPacketType {
  115.     my $self = shift;
  116.     my $data = shift;
  117.     (my $id, $data) = unpack( 'Sa*', $data );

  118.     if ( exists $gSMPacketID{$id} ){
  119.         return ($gSMPacketID{$id}->[0], $id, $data);
  120.     }
  121.     else{
  122.         return (undef,$id,$data);
  123.     }
  124. }

  125. sub SMParse {
  126.     my $self = shift;
  127.     my ($id, $data) = @_;
  128.     if ( exists $gSMPacketID{$id}
  129.         and ref $gSMPacketID{$id}->[1] eq 'CODE' ){
  130.         $gSMPacketID{$id}->[1]->( $self, $data );
  131.     }
  132. }

  133. sub CIPacketType {
  134.     my $self = shift;
  135.     my $data = shift;
  136.     (my $id, $data) = unpack( 'Ca*', $data );

  137.     if ( $id == CI_CRYPT_PACKET ){
  138.         $self->logmsg( '接收到加密报文,自动解密……' ) if not $self->{online};
  139.         my $len = length($data);
  140.         my $out = ' ' x $len;
  141.         $JzDecryptionFast->Call( $len, $data, $out );
  142.         (my $seq, $data) = unpack( 'i a*', $out );
  143.         $self->logmsg( "报文已解密。包序号:[$seq]。" ) if not $self->{online};
  144.         ($id, $data) = unpack( 'Ca*', $data );
  145.         if ( $id == ZS_COMPRESS_PACKET ){
  146.             $self->logmsg( '接收到压缩报文,自动解压……' ) if not $self->{online};
  147.             $out = UnCompress( $data );
  148.             if ( not defined $out ){
  149.                 $self->logmsg( '解压失败。' );
  150.                 return (undef, $id, $data);
  151.             }
  152.             $self->logmsg( sprintf "报文已解压。长度: [%d]。", length($out) ) if not $self->{online};
  153.             ($id, $data) = unpack( 'Ca*', $out );
  154.         }
  155.     }

  156.     if ( exists $gCIPacketID{$id} ){
  157.         return ($gCIPacketID{$id}->[0], $id, $data);
  158.     }
  159.     else{
  160.         return (undef,$id,$data);
  161.     }
  162. }

  163. sub CIParse {
  164.     my $self = shift;
  165.     my ($id, $data) = @_;
  166.     if ( exists $gCIPacketID{$id}
  167.         and ref $gCIPacketID{$id}->[1] eq 'CODE' ){
  168.         $gCIPacketID{$id}->[1]->( $self, $data );
  169.     }
  170. }

  171. sub Login {
  172.     my $self = shift;
  173.     return if @_ < 2;
  174.     my ($id, $passwd) = @_;
  175.     return if $id eq '';
  176.     return if $passwd eq '';

  177.     $self->{sm_wheel}->put( pack( 'ss/a*', SM_LOGIN_REQ,
  178.           pack( 's/a*', $id )
  179.         . pack( 's/a*', $passwd )
  180.         . pack( 's/a*', MAC ) ) );

  181.     $self->{userInfo}->{name} = $id;
  182.     $self->{userInfo}->{passwd} = $passwd;
  183.     $self->{main_state} = '连线 Session Manager Server';
  184.     $self->{state} = '正在登录';
  185.     $self->logmsg( "已发送登录请求。" );
  186. }

  187. sub LoginAck {
  188.     my $self = shift;
  189.     my $data = shift;
  190.     my $retcode = unpack( 's/a*', $data );
  191.     if ( $retcode eq '' ){
  192.         $self->logmsg( "登录失败,错误代码不明,重试一下。" );
  193.         return $self->Login( $self->{userInfo}{name}, $self->{userInfo}{passwd} );
  194.     }

  195.     $retcode = ord( $retcode );
  196.     if ( $retcode != 0 ){
  197.         my %errmsg = (
  198.             1 => '用户名不存在',
  199.             2 => '用户名不存在',
  200.             3 => '重复的连接',
  201.             4 => '帐户已过期',
  202.             5 => '数据库操作失败',
  203.             6 => '帐户无效',
  204.             7 => '密码错误',
  205.         );

  206.         # XXX: 退出或重试。
  207.         $self->exitmsg( "登录失败,错误代码:[$retcode] 错误信息:[$errmsg{$retcode}]。" );
  208.     }

  209.     $self->{state} = '登录成功';
  210.     $self->logmsg( "登录成功。" );

  211.     $self->SMStartGame(0,1);
  212. }

  213. sub SMStartGame {
  214.     my $self = shift;
  215.     my ($serverNo, $flag) = @_;

  216.     $self->{sm_wheel}->put( pack( 'ss/a*', SM_GAME_REQ, pack( 'ic', $serverNo, $flag ) ) );
  217.     $self->{state} = '开始游戏';
  218.     $self->logmsg( "已发送开始游戏请求。" );
  219. }

  220. sub ConnectInfoAck {
  221.     my $self = shift;
  222.     my $data = shift;
  223.     ($data) = unpack( 's/a*', $data );
  224.     (my ($strUserID, $count), $data) = unpack( 's/a*ca*', $data );
  225.     $self->dbgmsg( "用户名: [$strUserID] 服务器个数: [$count]" );
  226.     my @serverInfo = unpack( 's/a*s' x $count,  $data );
  227.     for( my $i=0; $i<@serverInfo; $i+=2 ){
  228.         my ($IP, $Port) = @serverInfo[$i, $i+1];
  229.         $self->dbgmsg( "IP: [$IP] Port: [$Port]" );
  230.     }
  231.     my ($IP, $Port) = @serverInfo;

  232.     $self->{state} = '正在连接';
  233.     $self->{main_state} = '连线 CharInfoServer';
  234.     $self->logmsg( '正在连接 CharInfoServer ……' );

  235.     $self->{heap}->{socket} = POE::Wheel::SocketFactory->new(
  236.         RemoteAddress  => $IP,
  237.         RemotePort     => $Port,
  238.         SuccessEvent   => 'charinfo_connected',
  239.         FailureEvent   => 'sock_error',
  240.     );
  241. }

  242. sub GetPublicKey {
  243.     my $self = shift;
  244.     my $type = shift;

  245.     if ( not grep { $type == $_ } (0,1,2) ){
  246.         $self->exitmsg( "类型错误。参数给出的类型是 [$type], 但可选值是 [0,1,2]" );
  247.     }

  248.     $self->{wheel}->put( pack( 'ss/a*s', 0x55aa,
  249.         pack( 'CC', CI_PUBLIC_KEY_REQ, $type ),
  250.         0xaa55 ) );

  251.     $self->{public_key_type} = $type;
  252.     $self->{state} = '请求公钥';
  253.     $self->logmsg( '正在请求加密公钥……' );
  254. }

  255. sub PublicKeyAck {
  256.     my $self = shift;
  257.     my $data = shift;

  258.     my ($key, $type) = unpack( 'a8c', $data );
  259.     $self->dbgmsg( '公钥: ' . unpack( 'H*', $key ) );
  260.     if ( $type != $self->{public_key_type} ){
  261.         $self->exitmsg( sprintf( "公钥类型不对。期望值: [%x] 实际得到: [%x]", $self->{public_key_type}, $type ) );
  262.     }

  263.     $self->{public_key} = $key;
  264.     $self->{state} = '得到公钥';
  265.     $JzSetPublicKey->Call( $key );
  266.     $self->logmsg( '已经获得加密公钥。' );

  267.     if ( $self->{main_state} eq '连线 CharInfoServer' ){
  268.         $self->CIAccountLogin();
  269.     }
  270.     elsif ( $self->{main_state} eq '连线 ZoneServer' ){
  271.         $self->GameStart(1,0);
  272.     }
  273.     else{
  274.         $self->exitmsg( '程序状态出错。' );
  275.     }
  276. }

  277. sub SendCryptPack {
  278.     my $self = shift;
  279.     my ($wheel, $data) = @_;

  280.     $data = pack( 'ia*', $self->{send_req}++, $data );
  281.     my $len = length($data);
  282.     my $crypt_data = ' ' x $len;
  283.     $JzEncryptionFast->Call( $len, $data, $crypt_data );
  284.     my $packet = pack( 'ss/a*s', 0x55aa,
  285.         pack( 'Ca*', CI_CRYPT_PACKET, $crypt_data ),
  286.         0xaa55 );
  287.     $wheel->put( $packet );
  288. }

  289. sub CIAccountLogin {
  290.     my $self = shift;

  291.     my $data = pack( 'cc/a*sa4', CI_ACCOUNT_LOGIN_REQ, $self->{userInfo}->{name}, 1, '' );
  292.     $self->SendCryptPack( $self->{wheel}, $data );

  293.     $self->{state} = '登录游戏';
  294.     $self->logmsg( '已发送登录游戏请求。' );
  295. }

  296. sub CIAccountLoginAck {
  297.     my $self = shift;
  298.     my $data = shift;

  299.     ( my ($result, $retcode), $data ) = unpack( 'c c a*', $data );

  300.     if ( $result != 1 ){
  301.         my %errmsg = (
  302.             1 => 'ID 错误',
  303.             2 => '未知错误',
  304.         );

  305.         # XXX: 退出或重试。
  306.         $self->exitmsg( "登录游戏失败!错误代码: [$retcode] 错误信息: [$errmsg{$retcode}]" );
  307.     }

  308.     my @packFormat = (
  309.         { charID    => 'c' },
  310.         { name      => 'c/A*' },
  311.         { STR       => 's' },
  312.         { CON       => 's' },
  313.         { DEX       => 's' },
  314.         { VOL       => 's' },
  315.         { WIS       => 's' },
  316.         { skin      => 'i' },
  317.         { hair      => 'i' },
  318.         { gender    => 'c' },
  319.         { face      => 'a10' },
  320.         { class     => 'c' },
  321.         { level     => 's' },
  322.         { HP        => 's' },
  323.         { PP        => 's' },
  324.         { SP        => 's' },
  325.         { MaxHP     => 's' },
  326.         { MaxPP     => 's' },
  327.         { MaxSP     => 's' },
  328.         { skillStr  => 'a60' },
  329.         { wearItemStr => 'a28' },
  330.         { serverIP  => 'c/A*' },
  331.         { serverPort => 's' },
  332.     );

  333.     my %charInfo;
  334.     my $format = join '', map{ (values %$_)[0] } @packFormat;
  335.     @charInfo{ map{ (keys %$_)[0] } @packFormat } = unpack( $format, $data );

  336.     $charInfo{face} = unpack( 'H*', $charInfo{face} );

  337.     while( ( my $item = substr( $charInfo{wearItemStr}, 0, 2, '' ) ) ne '' ){
  338.         push @{$charInfo{wearItems}}, [ unpack( 's', $item ) ];
  339.     }
  340.     delete $charInfo{wearItemStr};

  341.     while( ( my $skill = substr( $charInfo{skillStr}, 0, 3, '' ) ) ne '' ){
  342.         push @{$charInfo{skills}}, [ unpack( 'sc', $skill ) ];
  343.     }
  344.     delete $charInfo{skillStr};

  345.     $charInfo{className} = $ddClassCode[$charInfo{class}];
  346.     $charInfo{genderName} = $ddGenderCode[$charInfo{gender}];

  347.     $self->{state} = '登录成功';

  348.     # 在这里打印出友好的信息。
  349.     $self->logmsg( "登录成功。角色信息:\n" . '='x79 . "\n"
  350. . "角色: [$charInfo{name}] 性别: [$charInfo{genderName}] 职业: [$charInfo{className}] 级别: [$charInfo{level}]\n"
  351. . "力量: [$charInfo{STR}] 体质: [$charInfo{CON}] 敏捷: [$charInfo{DEX}] 智慧: [$charInfo{VOL}] 智力: [$charInfo{WIS}]\n"
  352. . "生命: [$charInfo{HP}/$charInfo{MaxHP}] 精神: [$charInfo{PP}/$charInfo{MaxPP}] 体力: [$charInfo{SP}/$charInfo{MaxSP}]"
  353. . "\n" . '='x79 );

  354.     $self->{zs_ip} = $charInfo{serverIP};
  355.     $self->{zs_port} = $charInfo{serverPort};
  356.     delete $charInfo{serverIP};
  357.     delete $charInfo{serverPort};
  358.     $self->{userInfo}->{chars}->{$charInfo{charID}} = \%charInfo;

  359.     $self->CIServerDisconnect();
  360. }

  361. sub CIServerDisconnect {
  362.     my $self = shift;

  363.     my $data = pack( 'C', CI_DISCONNECT_REQ );
  364.     $self->SendCryptPack( $self->{wheel}, $data );

  365.     $self->{state} = '断开连接';
  366.     $self->logmsg( '已发送断开角色服务器请求。' );
  367. }

  368. sub CIServerDisconnectAck {
  369.     my $self = shift;

  370.     $self->{state} = '断开连接';
  371.     $self->logmsg( '断开角色服务器' );

  372.     delete $self->{wheel};
  373.     delete $self->{heap}->{wheel};
  374.     delete $self->{heap}->{socket};

  375.     $self->{main_state} = '连线 ZoneServer';
  376.     $self->{state} = '进入游戏';
  377.     $self->logmsg( "正在连接 ZoneServer ……" );

  378.     $self->{heap}->{socket} = POE::Wheel::SocketFactory->new(
  379.         RemoteAddress  => $self->{zs_ip},
  380.         RemotePort     => $self->{zs_port},
  381.         SuccessEvent   => 'zoneserver_connected',
  382.         FailureEvent   => 'sock_error',
  383.     );
  384. }

  385. sub GameStart {
  386.     my $self = shift;
  387.     my ($serverID, $charID) = @_;

  388.     $self->{charID} = $charID;
  389.     $self->{charInfo} = $self->{userInfo}->{chars}->{$self->{charID}};

  390.     my $data = pack( 'C c/a* s c/a*', ZS_GAME_START_REQ, $self->{userInfo}{chars}{$charID}{name}, $serverID, $self->{userInfo}{name} );
  391.     $self->SendCryptPack( $self->{wheel}, $data );

  392.     $self->{state} = '进入游戏';
  393.     $self->logmsg( '已发送进入游戏请求。' );
  394. }

  395. sub GameStartAck {
  396.     my $self = shift;
  397.     my $data = shift;

  398.     my $result = substr( $data, 0, 1, '' );
  399.     if ( $result ne "\x01" ){
  400.         my $retcode = substr( $data, 0, 1 );
  401.         $self->exitmsg( sprintf "进入游戏失败。错误代码是:[0x%02X:%d]", ord($retcode), ord($retcode) );
  402.     }

  403.     # 已经成功进入游戏,可以断开 Session Manager Server 连接。
  404.     delete $self->{sm_wheel};
  405.     delete $self->{heap}->{sm_wheel};
  406.     delete $self->{heap}->{sm_socket};

  407.     $self->logmsg( '断开 Session Manager Server。' );

  408.     my ($zone, $uid, $posX, $posY, $dir, $gameHour, $gameMin) = unpack( 's i s s c c c', $data );

  409.     $self->{charInfo}->{uid} = $uid;
  410.     $self->{charInfo}->{x} = $posX;
  411.     $self->{charInfo}->{y} = $posY;
  412.     $self->{charInfo}->{dir} = $dir;
  413.     $self->{gameHour} = $gameHour;
  414.     $self->{gameMin} = $gameMin;

  415.     $self->{main_state} = '在线';
  416.     $self->{state} = '普通在线';
  417.     $self->{online} = 1;

  418.     $self->logmsg( "已进入游戏。" );
  419.     $self->logmsg( "位置:[$posX, $posY]; 面向:[$dir]; 游戏时间:[$gameHour 点 $gameMin 分]" );

  420.     # 现在人物已经站在地图上了。
  421.     # 先吃药,补满生命值。
  422.     $self->TakeMedicineIfPossible();

  423.     # XXX: 接下来开始打怪。
  424.     $self->{curTask} = '打怪';
  425.     $self->{charInfo}{attackRange} = 3;

  426.     # 启动打怪线程
  427.     $self->{attack_session} = POE::Session->create(
  428.         inline_states => {
  429.             _start          => \&attack_start,
  430.             attack_loop     => \&attack_loop,
  431.             add_attack_obj  => \&add_attack_obj,
  432.             del_attack_obj  => \&del_attack_obj,
  433.             _stop           => \&attack_stop,
  434.         },
  435.         args => [ $self ],
  436.     );
  437. }

  438. sub MoveAck {
  439.     my $self = shift;
  440.     my $data = shift;

  441.     my $result = substr( $data, 0, 1, '' );
  442.     if ( $result ne "\x01" ){
  443.         return;
  444.     }

  445.     my ($uid, $x, $y) = unpack( 'iss', $data );

  446.     if ( $uid == $self->{charInfo}{uid} ){
  447.         $self->{charInfo}{x} = $x;
  448.         $self->{charInfo}{y} = $y;
  449.         if ( $self->{curTask} eq '打怪' ){
  450.             if( $self->foundMonsterToAttack(0) ){
  451.                 $self->{state} = '普通在线';
  452.                 delete $self->{move_points};
  453.                 return;
  454.             }
  455.         }
  456.         usleep(1000 * 300);
  457.         $self->ContinueMove();
  458.     }

  459.     # XXX: 根据 UID 来判断是怪物还是玩家,不科学。
  460.     return if $uid < 20000;

  461.     $self->{npcinfo}{$uid}{x} = $x;
  462.     $self->{npcinfo}{$uid}{y} = $y;
  463. }

  464. sub NpcInfo {
  465.     my $self = shift;
  466.     my $data = shift;

  467.     (my ($mode, $nid), $data) = unpack( 'c s a*', $data );
  468.     if ( $mode == 2 ){
  469.         $self->logmsg( "$nid 嗖~地一声消失了……" );
  470.         delete $self->{npcinfo}{$nid};
  471.         return;
  472.     }

  473.     my ($pid, $name, $x, $y, $state, $type, $MaxHP, $HP, $speed, $color, $rest)
  474.         = unpack( 's c/A* s s c c s s x8 s c a*', $data );
  475.     my $questSay;
  476.     if ( $type == 8 ){
  477.          $questSay = unpack( 'x4 s', $rest );
  478.     }
  479.     else{
  480.          $questSay = unpack( 's', $rest );
  481.     }

  482.     my $typeName = $ddNpcTypeCode[$type];
  483.     $self->logmsg( "发现了一个$typeName,名称 [$name] HP [$HP] 位置 [$x,$y]" );
  484.     $self->{npcinfo}{$nid} = {
  485.         name => $name,
  486.         x => $x,
  487.         y => $y,
  488.     };

  489.     my $total = keys %{$self->{npcinfo}};
  490.     $self->dbgmsg( "有 $total 个怪物信息。" );
  491. }

  492. sub SystemMsg {
  493.     my $self = shift;
  494.     my $data = shift;

  495.     my ($type, $msg) = unpack( 'c c/A*', $data );
  496.     my $typeName = $ddSystemMsgTypeCode[ $type ];

  497.     $self->logmsg( "接收到$typeName消息: [$msg]" );
  498. }

  499. sub WeatherChange {
  500.     my $self = shift;
  501.     my $data = shift;

  502.     my ($type, $value) = unpack( 'cc', $data );
  503.     if ( $type == 1 ){
  504.         $self->logmsg( "$value 点了。" );
  505.     }
  506.     elsif ( $type == 2 ){
  507.         if ( $value ){
  508.             $self->logmsg( '下雨了。' );
  509.         }
  510.         else{
  511.             $self->logmsg( '雨停了。' );
  512.         }
  513.     }
  514. }

  515. sub UserStatusUpdate {
  516.     my $self = shift;
  517.     my $data = shift;

  518.     my @packFormat = (
  519.         { PA        => 's' },
  520.         { STR       => 's' },
  521.         { CON       => 's' },
  522.         { DEX       => 's' },
  523.         { VOL       => 's' },
  524.         { WIS       => 's' },
  525.         { HP        => 's' },
  526.         { PP        => 's' },
  527.         { SP        => 's' },
  528.         { MaxHP     => 's' },
  529.         { MaxPP     => 's' },
  530.         { MaxSP     => 's' },
  531.         { Damage    => 's' },
  532.         { Defense   => 's' },
  533.         { skillPoint=> 's' },
  534.         { skillClass=> 'c' },
  535.         { skillStr  => 'a15' },
  536.     );

  537.     my %charInfo;
  538.     my $format = join '', map{ (values %$_)[0] } @packFormat;
  539.     @charInfo{ map{ (keys %$_)[0] } @packFormat } = unpack( $format, $data );
  540.     while( ( my $skill = substr( $charInfo{skillStr}, 0, 3, '' ) ) ne '' ){
  541.         push @{$charInfo{skills}}, [ unpack( 'sc', $skill ) ];
  542.     }

  543.     delete $charInfo{skillStr};

  544.     my @keys = qw(PA STR CON DEX VOL WIS HP PP SP MaxHP MaxPP MaxSP Damage Defense skillPoint);
  545.     foreach my $key (@keys){
  546.         $self->{charInfo}->{$key} = $charInfo{$key};
  547.     }

  548.     # TODO: 需要继续单独处理一下 SkillClass and skillStr
  549. }

  550. sub UserInfo {
  551.     my $self = shift;
  552.     my $data = shift;

  553.     (my ($mode, $uid), $data) = unpack( 'C I a*', $data );
  554.     if ( $mode == 2 ){
  555.         $self->logmsg( "$uid 嗖~地一声消失了。" );
  556.         return;
  557.     }

  558.     my @packFormat = (
  559.         { name      => 'c/A*' },
  560.         { x         => 's' },
  561.         { y         => 's' },
  562.         { skin      => 'i' },
  563.         { hair      => 'i' },
  564.         { gender    => 'c' },
  565.         { face      => 'a10' },
  566.         { equipItem => 'a20' },
  567.         { HP        => 's' },
  568.         { MaxHP     => 's' },
  569.         { dir       => 'c' },
  570.         { abnormal  => 'i' },
  571.         { cityRank  => 's' },
  572.         { guildIndex => 'i' },
  573.         { guildName => 'c/A*' },
  574.         { version   => 's' },
  575.         { class     => 'c' },
  576.         { killState => 'c' },
  577.         { loveName  => 'c/A*' },
  578.         { ebodyItem => 'a8' },
  579.     );

  580.     my %userInfo;
  581.     my $format = join '', map{ (values %$_)[0] } @packFormat;
  582.     @userInfo{ map{ (keys %$_)[0] } @packFormat } = unpack( $format, $data );

  583.     $self->logmsg( "接收到人物 $userInfo{name} 的概要信息。" );
  584.     if ( $uid == $self->{charInfo}->{uid} ){
  585.         my @keys = qw(name x y equipItem HP MaxHP dir cityRank guildIndex guildName class loveName ebodyItem);
  586.         foreach my $key (@keys){
  587.             $self->{charInfo}->{$key} = $userInfo{$key};
  588.         }
  589.     }
  590.     else {
  591.         $self->{npcinfo}{$uid} = {
  592.             name => $userInfo{name},
  593.             x => $userInfo{x},
  594.             y => $userInfo{y},
  595.         };
  596.     }
  597. }

  598. sub CharInfo {
  599.     my $self = shift;
  600.     my $data = shift;

  601.     my @packFormat = (
  602.         { name      => 'c/A*' },
  603.         { STR       => 'S' },
  604.         { CON       => 'S' },
  605.         { DEX       => 'S' },
  606.         { VOL       => 'S' },
  607.         { WIS       => 'S' },
  608.         { abilitySum=> 'S' },
  609.         { skin      => 'I' },
  610.         { hair      => 'I' },
  611.         { gender    => 'c' },
  612.         { face      => 'a10' },
  613.         { Exp       => 'I' },
  614.         { XP        => 'I' },
  615.         { skillPoint=> 'S' },
  616.         { PA        => 'S' },
  617.         { MaxHP     => 'S' },
  618.         { HP        => 'S' },
  619.         { MaxPP     => 'S' },
  620.         { PP        => 'S' },
  621.         { MaxSP     => 'S' },
  622.         { SP        => 'S' },
  623.         { DN        => 'I' },
  624.         { cityRank  => 'S' },
  625.         { level     => 'S' },
  626.         { class     => 'C' },
  627.         { skillStr  => 'a60' },
  628.         { havePsi   => 'C' },
  629.     );

  630.     my %charInfo;
  631.     my $format = join '', map{ (values %$_)[0] } @packFormat;
  632.     (@charInfo{ map{ (keys %$_)[0] } @packFormat }, $data) = unpack( $format . 'a*', $data );

  633.     while( ( my $skill = substr( $charInfo{skillStr}, 0, 3, '' ) ) ne '' ){
  634.         push @{$charInfo{skills}}, [ unpack( 'sc', $skill ) ];
  635.     }
  636.     delete $charInfo{skillStr};

  637.     for( my $i=0; $i<$charInfo{havePsi}; $i++ ){
  638.         my $userPsiStr = substr( $data, 0, 3, '' );
  639.         push @{$charInfo{userPsi}}, [ unpack( 'SC', $userPsiStr ) ];
  640.     }

  641.     # 继续处理剩下的字段
  642.     @packFormat = (
  643.         { itemStr   => sprintf( 'a%d', 39 * 22 ) },
  644.         { expNext   => 'I'  },
  645.         { abnormal  => 'I'  },
  646.         { changeClassStr => 'a8' },
  647.         { _pad      => 'a12' },
  648.         { guildIndex=> 'i'  },
  649.         { guildName => 'c/A*' },
  650.         { version   => 'S' },
  651.     );

  652.     $format = join '', map{ (values %$_)[0] } @packFormat;
  653.     @charInfo{ map{ (keys %$_)[0] } @packFormat } = unpack( $format, $data );

  654.     while( ( my $item = substr( $charInfo{itemStr}, 0, 22, '' ) ) ne '' ){
  655.         my ($level, $sid, $duration, $bullNum, $count, $magicStr, $IQ)
  656.             = unpack( 'sssssa11c', $item );
  657.         push @{$charInfo{items}}, {
  658.             sid   => $sid,
  659.             level => $level,
  660.             duration => $duration,
  661.             bullNum => $bullNum,
  662.             count => $count,
  663.             magicStr => $magicStr,
  664.             IQ => $IQ
  665.         };
  666.     }
  667.     delete $charInfo{itemStr};
  668.    
  669.     $charInfo{_pad} = unpack( 'H*', $charInfo{_pad} );

  670.     foreach my $key (keys %charInfo){
  671.         $self->{charInfo}->{$key} = $charInfo{$key};
  672.     }

  673.     $self->{charInfo}->{repairHP} = [];
  674.     $self->{charInfo}->{repairPP} = [];
  675.     $self->{charInfo}->{repairSP} = [];

  676.     # 检查有没有药
  677.     foreach my $slot (0..$#{$self->{charInfo}->{items}}){
  678.         my $sid = $self->{charInfo}->{items}->[$slot]->{sid};
  679.         my $count = $self->{charInfo}->{items}->[$slot]->{count};
  680.         next if ( $sid == -1 );
  681.         if ( not exists $ddItemInfo->{$sid} ){
  682.             $self->logmsg( "严重错误:物品 $sid 不存在。" );
  683.             next;
  684.         }
  685.         # 跳过不是药品的物品
  686.         next if ( $ddItemInfo->{$sid}->{byWear} != 101 );
  687.         if ( $count <= 0 ){
  688.             $self->logmsg( "严重错误:物品 $ddItemInfo->{$sid}($sid) 的个数为 $count" );
  689.             next;
  690.         }
  691.         # 可以恢复生命值的,将它的 slot 和可恢复值记录在这个数组里。
  692.         if ( $ddItemInfo->{$sid}->{sRepairHP} > 0 ){
  693.             push @{$self->{charInfo}->{repairHP}}, {
  694.                 slot => $slot,
  695.                 value => $ddItemInfo->{$sid}->{sRepairHP},
  696.             };
  697.         }
  698.         # 可以恢复精神值的,将它的 slot 和可恢复值记录在这个数组里。
  699.         if ( $ddItemInfo->{$sid}->{sRepairPP} > 0 ){
  700.             push @{$self->{charInfo}->{repairPP}}, {
  701.                 slot => $slot,
  702.                 value => $ddItemInfo->{$sid}->{sRepairPP},
  703.             };
  704.         }
  705.         # 可以恢复体力值的,将它的 slot 和可恢复值记录在这个数组里。
  706.         if ( $ddItemInfo->{$sid}->{sRepairSP} > 0 ){
  707.             push @{$self->{charInfo}->{repairSP}}, {
  708.                 slot => $slot,
  709.                 value => $ddItemInfo->{$sid}->{sRepairSP},
  710.             };
  711.         }
  712.     }
  713.     $self->logmsg( "接收到人物 $charInfo{name} 的详细信息。" );
  714. }

  715. sub foundMonsterToAttack {
  716.     my $self = shift;
  717.     my $allowMove = shift;
  718.     my $count = 0;
  719.     my $nearest;

  720.     foreach my $nid (keys %{$self->{npcinfo}}) {
  721.         next if $nid < 20000 || $nid > 29999;
  722.         my $x = $self->{npcinfo}{$nid}{x};
  723.         my $y = $self->{npcinfo}{$nid}{y};
  724.         if ( abs($x-$self->{charInfo}{x}) < $self->{charInfo}{attackRange}
  725.             and abs($y-$self->{charInfo}{y}) < $self->{charInfo}{attackRange}  ){
  726.             $self->Attack( $nid );
  727.             $count ++;
  728.         }
  729.         elsif( not defined $nearest ){
  730.             $nearest = $self->{npcinfo}{$nid};
  731.         }
  732.         else{
  733.             my $interval1 = abs($nearest->{x} - $self->{charInfo}{x})
  734.                            +abs($nearest->{y} - $self->{charInfo}{y});
  735.             my $interval2 = abs($self->{npcinfo}{$nid}{x} - $self->{charInfo}{x})
  736.                            +abs($self->{npcinfo}{$nid}{y} - $self->{charInfo}{y});
  737.             if ( $interval2 < $interval1 ){
  738.                 $nearest = $self->{npcinfo}{$nid};
  739.             }
  740.         }
  741.     }

  742.     if ( $count == 0 and $allowMove and $nearest ){
  743.         $self->GoTo( $nearest->{x}, $nearest->{y} );
  744.     }

  745.     return $count;
  746. }

  747. sub Attack {
  748.     my $self = shift;
  749.     my $uid = shift;
  750.     $Kernel->post( $self->{attack_session}, 'add_attack_obj', $uid );
  751. }

  752. sub GoTo {
  753.     my $self = shift;
  754.     my ($dst_x, $dst_y) = @_;

  755.     return if $self->{state} eq '正在移动';
  756.     $self->{state} = '正在移动';

  757.     $self->{move_points} = [];

  758.     my $x = $self->{charInfo}{x};
  759.     my $y = $self->{charInfo}{y};

  760.     my $dx = $dst_x - $x;
  761.     my $dy = $dst_y - $y;
  762.     my $ix = 1;
  763.     my $iy = 1;
  764.     if ( $dx < 0 ){
  765.         $dx = -$dx;
  766.         $ix = -1;
  767.     }
  768.     if ( $dy < 0 ){
  769.         $dy = -$dy;
  770.         $iy = -1;
  771.     }

  772.     if ( $dx > $dy ){
  773.         my $e = -$dx;
  774.         for( my $i=0; $i<=$dx; $i++ ){
  775.             push @{$self->{move_points}}, [$x,$y];
  776.             $x += $ix;
  777.             $e += 2*$dy;
  778.             if ( $e >= 0 ){
  779.                 $y += $iy; $e-=2*$dx;
  780.             }
  781.             else{
  782.                 $i++;
  783.                 $x += $ix;
  784.                 $e += 2*$dy;
  785.             }
  786.         }
  787.     }
  788.     else{
  789.         my $e = -$dy;
  790.         for( my $i=0; $i<=$dy; $i++ ){
  791.             push @{$self->{move_points}}, [$x,$y];
  792.             $y += $iy;
  793.             $e += 2*$dx;
  794.             if ( $e >= 0 ){
  795.                 $x += $ix; $e-=2*$dy;
  796.             }
  797.             else{
  798.                 $i++;
  799.                 $y += $iy;
  800.                 $e += 2*$dy;
  801.             }
  802.         }
  803.     }

  804.     shift @{$self->{move_points}};
  805.     $self->ContinueMove();
  806. }

  807. sub ContinueMove {
  808.     my $self = shift;

  809.     return if $self->{state} ne '正在移动';
  810.     return if not defined $self->{move_points};

  811.     if ( @{$self->{move_points}} < 1 ){
  812.         delete $self->{move_points};
  813.         $self->{state} = '普通在线';
  814.         $self->logmsg( "跑动结束" );
  815.         return;
  816.     }

  817.     my $x = $self->{charInfo}{x};
  818.     my $y = $self->{charInfo}{y};


  819.     my $point = shift @{$self->{move_points}};
  820.     my ($x1, $y1) = @$point;

  821.     if ( $self->{charInfo}{SP} < 10 ){
  822.         $self->logmsg( "走动 cur: $x,$y next: $x1,$y1" );
  823.         my $data = pack( 'cssc', ZS_MOVE_REQ, $x1, $y1, 1 );
  824.         $self->SendCryptPack( $self->{wheel}, $data );
  825.         $self->TakeMedicineIfPossible();
  826.         return;
  827.     }

  828.     my ($x2, $y2) = ($x1, $y1);
  829.     if ( @{$self->{move_points}} > 0 ){
  830.         $point = shift @{$self->{move_points}};
  831.         ($x2, $y2) = @$point;
  832.     }

  833.     $self->logmsg( "跑动 cur: $x,$y next1: $x1,$y1 next2: $x2,$y2" );
  834.     my $data = pack( 'cssssc', ZS_RUN_REQ, $x1, $y1, $x2, $y2, 1 );
  835.     $self->SendCryptPack( $self->{wheel}, $data );
  836. }

  837. sub RunAck {
  838.     my $self = shift;
  839.     my $data = shift;

  840.     my $result = ord( substr( $data, 0, 1, '' ) );
  841.     if ( $result != 1 ){
  842.         $self->{state} = '普通在线';
  843.         $self->dbgmsg( "没跑成功。" );
  844.         delete $self->{move_points};
  845.         return;
  846.     }

  847.     my ($uid, $x1, $y1, $x2, $y2) = unpack( 'Issss', $data );
  848.     if ( $x2 != -1 and $y2 != -1 ){
  849.         $self->{npcinfo}{$uid}{x} = $x2;
  850.         $self->{npcinfo}{$uid}{y} = $y2;
  851.         $self->logmsg( "$uid 经由 $x1, $y1 跑到 $x2, $y2" );
  852.     }
  853.     else{
  854.         $self->{npcinfo}{$uid}{x} = $x1;
  855.         $self->{npcinfo}{$uid}{y} = $y1;
  856.         $self->logmsg( "$uid 跑到 $x1, $y1" );
  857.     }

  858.     if ( $uid == $self->{charInfo}{uid} ){
  859.         $self->{charInfo}{x} = $x2 != -1 ? $x2 : $x1;
  860.         $self->{charInfo}{y} = $y2 != -1 ? $y2 : $y1;
  861.         if ( $self->{curTask} eq '打怪' ){
  862.             if( $self->foundMonsterToAttack(0) ){
  863.                 $self->{state} = '普通在线';
  864.                 delete $self->{move_points};
  865.                 return;
  866.             }
  867.         }
  868.         usleep(1000 * 300);
  869.         $self->ContinueMove();
  870.     }
  871. }

  872. sub ChatResult {
  873.     my $self = shift;
  874.     my $data = shift;

  875.     my $result = ord( substr( $data, 0, 1, '' ) );
  876.     if ( $result != 1 ){
  877.         my $errCode = ord( substr( $data, 0, 1, '' ) );
  878.         $self->dbgmsg( "接收到聊天结果响应报文,但是包含有错误代码。" );
  879.         return;
  880.     }

  881.     my ($type, $uid, $name, $msg) = unpack( 'C I c/A* c/A*', $data );
  882.     $self->logmsg( "接收到来自于 $name 的消息: [$msg]" );

  883.     if ( $name eq 'wxh' ){
  884.         if ( $msg =~ /goto (-?\d+) (-?\d+)/ ){
  885.             my ($x, $y) = ($self->{charInfo}{x}+$1, $self->{charInfo}{y}+$2);
  886.             $self->dbgmsg( "管理员指令:立即跑步前往 => $x,$y" );
  887.             $self->{curTask} = '管理员任务';
  888.             $self->{state} = '普通在线';
  889.             $self->GoTo( $x, $y );
  890.         }
  891.         elsif ( $msg eq 'dg' ){
  892.             $self->dbgmsg( "管理员指令:开始打怪。" );
  893.             $self->{curTask} = '打怪';
  894.         }
  895.     }
  896. }

  897. sub SendAttackReq {
  898.     my $self = shift;
  899.     my $uid = shift;

  900.     my $data = pack( 'cs', ZS_ATTACK_REQ, $uid );
  901.     $self->SendCryptPack( $self->{wheel}, $data );
  902. }

  903. sub AttackAck {
  904.     my $self = shift;
  905.     my $data = shift;

  906.     my $result = ord( substr( $data, 0, 1, '' ) );
  907.     if ( $result == 0 ){
  908.         my %errMsg = (
  909.             0   => '攻击延时太短',
  910.             1   => '距离太远够不着打',
  911.         );
  912.         my $errcode = ord( $data );
  913.         if ( exists( $errMsg{$errcode} ) ){
  914.             $self->logmsg( "因为$errMsg{$errcode},所以没打着" );
  915.         }
  916.         else{
  917.             $self->logmsg( "因为 $errcode,没打着" );
  918.         }
  919.         return;
  920.     }

  921.     if ( $result == 2 ){
  922.         my ($src, $dst) = unpack( 'ii', $data );
  923.         if ( $src == $self->{charInfo}{uid} ){
  924.             $self->logmsg( "你想打 $dst,结果它灵巧地躲开了" );
  925.             $Kernel->post( $self->{attack_session}, 'touch_attack_obj', $dst );
  926.         }
  927.         elsif ( $dst == $self->{charInfo}{uid} ){
  928.             $self->logmsg( "$src 想打你,结果你灵巧地躲开了" );
  929.             $self->Attack( $src );
  930.         }
  931.         else{
  932.             # 别人打架的就不用报告了。
  933.         }
  934.         return;
  935.     }

  936.     if ( $result != 1 && $result != 3 ){
  937.         $self->logmsg( "接收到攻击响应报文,但不知道是什么类型。请联系开发人员,谢谢合作!" );
  938.         $self->pkgDump( $data, '未知攻击' );
  939.         return;
  940.     }

  941.     my ($src, $dst, $HP, $MaxHP) = unpack( 'iiss', $data );
  942.     if ( $src == $self->{charInfo}{uid} ){
  943.         $self->logmsg( "你打中了 $dst, 它的 HP: [$HP/$MaxHP]" );
  944.         $Kernel->post( $self->{attack_session}, 'touch_attack_obj', $dst );
  945.     }
  946.     elsif ( $dst == $self->{charInfo}{uid} ) {
  947.         $self->logmsg( "$src 打中了你, 你的 HP: [$HP/$MaxHP]" );
  948.         $self->{charInfo}{HP} = $HP;
  949.         $self->{charInfo}{MaxHP} = $MaxHP;
  950.         $self->Attack( $src ); # 还击
  951.     }
  952.     else{
  953.         # 别人打架
  954.     }

  955.     return;
  956. }

  957. sub DeadAck {
  958.     my $self = shift;
  959.     my $data = shift;

  960.     my ($uid, $x, $y) = unpack( 'sss', $data );

  961.     $self->logmsg( "$uid 在 $x,$y 倒下了。" );
  962.     $self->logmsg( "删除攻击对象。" );
  963.     $Kernel->post( $self->{attack_session}, 'del_attack_obj', $uid );
  964.     delete $self->{npcinfo}{$uid};
  965. }

  966. sub SetHP {
  967.     my $self = shift;
  968.     my $data = shift;

  969.     my $hp = unpack( 's', $data );

  970.     $hp -= $self->{charInfo}{HP};
  971.     $self->{charInfo}{HP} += $hp;
  972.     if ( $hp < 0 ){
  973.         $hp = -$hp;
  974.         $self->logmsg( "生命值减少了 $hp,目前是 $self->{charInfo}{HP}/$self->{charInfo}{MaxHP}" );
  975.         $self->TakeMedicineIfPossible(); # 如果能吃得下药,就吃药。
  976.     }
  977.     elsif ( $hp > 0 ){
  978.         $self->logmsg( "生命值增加了 $hp,目前是 $self->{charInfo}{HP}/$self->{charInfo}{MaxHP}" );
  979.     }
  980. }

  981. sub SetExp {
  982.     my $self = shift;
  983.     my $data = shift;

  984.     my ($type, $rest) = unpack( 'ca*', $data );

  985.     if ( $type == 0 ){
  986.         $self->dbgmsg( "接收到 SET_EXP 报文,但是类型标记为错误。" );
  987.         return;
  988.     }

  989.     if ( $type != 1 and $type != 2 ){
  990.         $self->dbgmsg( "接收到 SET_EXP 报文,但是类型无法识别。" );
  991.         return;
  992.     }

  993.     (my $exp, $rest) = unpack( 'Ia*', $rest );
  994.     if ( not defined $self->{charInfo}{Exp} ){
  995.         $self->dbgmsg( "warning! exp not defined" );
  996.     }
  997.     $exp -= $self->{charInfo}{Exp};
  998.     $self->{charInfo}{Exp} += $exp;
  999.     if ($type == 1){
  1000.         $self->logmsg( "获得 $exp 点经验值,距离升级还需要", $self->{charInfo}{expNext} - $self->{charInfo}{Exp}, "点经验值。" );
  1001.     }
  1002.     else{
  1003.         my ($expNext, $level, $skillPoint, $PA) = unpack( 'Isss', $rest );
  1004.         $self->{charInfo}{expNext} = $expNext;
  1005.         $self->{charInfo}{level} = $level;
  1006.         $self->{charInfo}{skillPoint} = $skillPoint;
  1007.         $self->{charInfo}{PA} = $PA;
  1008.         $self->logmsg( "升级了。" );
  1009.     }
  1010. }

  1011. sub SetSP {
  1012.     my $self = shift;
  1013.     my $data = shift;

  1014.     my $sp = unpack( 's', $data );

  1015.     $sp -= $self->{charInfo}{SP};
  1016.     $self->{charInfo}{SP} += $sp;
  1017.     if ( $sp < 0 ){
  1018.         $sp = -$sp;
  1019.         $self->logmsg( "体力减少了 $sp,目前是 $self->{charInfo}{SP}/$self->{charInfo}{MaxSP}" );
  1020.         $self->TakeMedicineIfPossible(); # 如果能吃得下药,就吃药。
  1021.     }
  1022.     elsif ( $sp > 0 ){
  1023.         $self->logmsg( "体力增加了 $sp,目前是 $self->{charInfo}{SP}/$self->{charInfo}{MaxSP}" );
  1024.     }
  1025. }

  1026. sub TakeMedicineIfPossible {
  1027.     my $self = shift;

  1028.     # 先考虑生命值
  1029.     foreach my $item ( sort {$a->{value} <=> $b->{value}} @{$self->{charInfo}->{repairHP}} ){
  1030.         if ( $item->{value} <= $self->{charInfo}->{MaxHP} - $self->{charInfo}->{HP} ){
  1031.             $self->ItemUse( $item->{slot} );
  1032.             return;
  1033.         }
  1034.     }

  1035.     # 再考虑精神值
  1036.     foreach my $item ( sort {$a->{value} <=> $b->{value}} @{$self->{charInfo}->{repairPP}} ){
  1037.         if ( $item->{value} <= $self->{charInfo}->{MaxPP} - $self->{charInfo}->{PP} ){
  1038.             $self->ItemUse( $item->{slot} );
  1039.             return;
  1040.         }
  1041.     }

  1042.     # 接下来考虑体力值
  1043.     foreach my $item ( sort {$a->{value} <=> $b->{value}} @{$self->{charInfo}->{repairSP}} ){
  1044.         if ( $item->{value} <= $self->{charInfo}->{MaxSP} - $self->{charInfo}->{SP} ){
  1045.             $self->ItemUse( $item->{slot} );
  1046.             return;
  1047.         }
  1048.     }
  1049. }

  1050. sub ItemUse {
  1051.     my $self = shift;
  1052.     my $slot = shift;

  1053.     return if ( $self->{charInfo}->{items}->[$slot]->{count} <= 0 );

  1054.     $self->logmsg( "服用药品", $ddItemInfo->{$self->{charInfo}->{items}->[$slot]->{sid}}->{strName} );
  1055.     my $data = pack( 'ccc', ZS_ITEM_USE_REQ, 1, $slot );
  1056.     $self->SendCryptPack( $self->{wheel}, $data );
  1057. }

  1058. sub ItemUseAck {
  1059.     my $self = shift;
  1060.     my $data = shift;

  1061.     my $result = ord( substr( $data, 0, 1, '' ) );
  1062.     if ( $result != 1 ){
  1063.         $self->logmsg( "物品使用失败。" );
  1064.         return;
  1065.     }

  1066.     my ($mode, $SP, $HP, $PP, undef, $slot, $level, $sid, $duration, $bullNum, $count, $magicStr, $IQ)
  1067.         = unpack( 'c s s s s c sssssa11c', $data );

  1068.     if ( $mode == 1 ){
  1069.         $self->logmsg( "物品使用结果: 体力恢复到 $SP。" );
  1070.     }
  1071.     elsif ( $mode == 2 ){
  1072.         $self->logmsg( "物品使用结果: 精神恢复到 $PP。" );
  1073.     }
  1074.     elsif ( $mode == 4 ){
  1075.         $self->logmsg( "物品使用结果: 生命恢复到 $HP。" );
  1076.     }
  1077.     elsif ( $mode == 7 ){
  1078.         $self->logmsg( "物品使用结果: 体力恢复到 $SP, 精神恢复到 $PP, 生命恢复到 $HP。" );
  1079.     }
  1080.     else{
  1081.         $self->logmsg( "汗~我究竟吃了什么药?似乎吃错药了。" );
  1082.     }

  1083.     $self->{charInfo}->{items}->[$slot] = {
  1084.         level => $level,
  1085.         sid => $sid,
  1086.         duration => $duration,
  1087.         bullNum => $bullNum,
  1088.         count => $count,
  1089.         magicStr => $magicStr,
  1090.         IQ => $IQ,
  1091.     };
  1092. }

  1093. sub UsePotionNotice {
  1094.     my $self = shift;
  1095.     my $data = shift;

  1096.     my ($uid, $type) = unpack( 'Ic', $data );

  1097.     my $name = $self->getUserName( $uid );

  1098.     my %potionName = (
  1099.         1 => '生命恢复剂',
  1100.         2 => '精神恢复剂',
  1101.         3 => '体力恢复剂',
  1102.         4 => '全面恢复剂',
  1103.         9 => '毒药',
  1104.     );

  1105.     my $potionName = $potionName{$type} || '不知什么药丸。';
  1106.     $self->logmsg( "$name 吃下了 $potionName" );
  1107. }

  1108. sub FieldItemInfo {
  1109.     my $self = shift;
  1110.     my $data = shift;

  1111.     my ($loopCount, $mode, $x, $y, $sid, $count, $IQ, $uid) = unpack( 'sCsssICa*', $data );
  1112.     my $name = $sid;
  1113.     if ( exists $ddItemInfo->{$sid} ){
  1114.         $name = $ddItemInfo->{$sid}->{strName};
  1115.     }
  1116.     if ( $mode == 1 ){
  1117.         $self->logmsg( "在 $x, $y 发现 $count 个 $name。" );
  1118.     }
  1119.     elsif ( $mode == 2 ){
  1120.         $self->logmsg( "$x, $y 处的 $count 个 $name 消失了。" );
  1121.     }
  1122.     elsif ( $mode == 3 ){
  1123.         $uid = unpack( 'I', $uid );
  1124.         $self->logmsg( "$x, $y 处的 $count 个 $name 被 " . $self->getUserName($uid) . " 捡走了。" );
  1125.     }
  1126.     else{
  1127.         $self->dbgmsg( "接受到错误的 FIELD_ITEM_INFO 报文。Mode = [$mode]" );
  1128.     }
  1129. }

  1130. sub attack_start {
  1131.     my ($kernel, $heap, $gameClient ) = @_[KERNEL, HEAP, ARG0];

  1132.     $heap->{game_client} = $gameClient;
  1133.     $heap->{timer} = 0;
  1134.     $kernel->yield( 'attack_loop' );
  1135. }

  1136. sub attack_loop {
  1137.     my ($kernel, $heap ) = @_[KERNEL, HEAP];
  1138.     foreach my $uid ( @{$heap->{attack_list}} ){
  1139.         if ( !defined $heap->{attack_sent}{$uid} ||
  1140.             $heap->{timer} - $heap->{attack_sent}{$uid} > 10 ){
  1141.             $heap->{game_client}->logmsg( "攻击 $uid");
  1142.             $heap->{game_client}->SendAttackReq( $uid );
  1143.             $heap->{attack_sent}{$uid} = $heap->{timer};
  1144.         }
  1145.     }

  1146.     $heap->{timer} ++;
  1147.     $heap->{game_client}->foundMonsterToAttack(1);

  1148.     # 不论有无怪物,线程都要空转。
  1149.     usleep(1000 * 200);
  1150.     $kernel->yield( 'attack_loop' );
  1151. }

  1152. sub add_attack_obj {
  1153.     my ($kernel, $heap, $uid) = @_[KERNEL, HEAP, ARG0];
  1154.     my ($index) = grep { $heap->{attack_list}->[$_] == $uid } (0..$#{$heap->{attack_list}});
  1155.     return if defined $index;
  1156.     $heap->{game_client}->logmsg( "打 $uid" );
  1157.     push @{$heap->{attack_list}}, $uid;
  1158. }

  1159. sub del_attack_obj {
  1160.     my ($heap, $uid) = @_[HEAP, ARG0];
  1161.     $heap->{game_client}->logmsg( "查找攻击对象 $uid。" );
  1162.     my ($index) = grep { $heap->{attack_list}->[$_] == $uid } (0..$#{$heap->{attack_list}});
  1163.     return if not defined $index;
  1164.     $heap->{game_client}->logmsg( "找到了,位置在 $index。" );
  1165.     splice @{$heap->{attack_list}}, $index, 1;
  1166. }

  1167. sub touch_attack_obj {
  1168.     my ($heap, $uid) = @_[HEAP, ARG0];
  1169.     delete $heap->{attack_sent}{$uid};
  1170. }

  1171. sub attack_stop {
  1172.     my $heap = $_[HEAP];
  1173.     $heap->{game_client}->{state} = '普通在线';
  1174.     delete $_[HEAP]->{game_client}->{attack_session};
  1175. }

  1176. sub getUserName {
  1177.     my $self = shift;
  1178.     my $uid = shift;

  1179.     if ( $uid == $self->{charInfo}{uid} ){
  1180.         return $self->{charInfo}{name};
  1181.     }
  1182.     elsif ( exists $self->{npcinfo}{$uid} ){
  1183.         return $self->{npcinfo}{$uid}{name};
  1184.     }
  1185.     else{
  1186.         return $uid;
  1187.     }
  1188. }

  1189. sub logmsg {
  1190.     my $self = shift;
  1191.     return if @_ == 0;
  1192.     printf "%s %s: %s\n", &getTimeStr(), $self->{state}, join ' ', @_;
  1193. }

  1194. sub exitmsg {
  1195.     my $self = shift;
  1196.     return if @_ == 0;
  1197.     printf "%s %s: %s\n", &getTimeStr(), $self->{state}, join ' ', @_;
  1198.     die "\n";
  1199. }

  1200. sub dbgmsg {
  1201.     my $self = shift;
  1202.     return if @_ == 0;
  1203.     return if $Debug != 1;
  1204.     printf "%s %s: %s\n", &getTimeStr(), $self->{state}, join ' ', @_;
  1205. }

  1206. sub pkgDump {
  1207.     my $self = shift;
  1208.     my $data = shift;
  1209.     my $title = shift || '';
  1210.     return if not defined $data;
  1211.     return if $Debug != 1;

  1212.     if ( $title ){
  1213.         $title = sprintf( "%-9s ", $title );
  1214.         $title .= '=' x 67;
  1215.     }
  1216.     else{
  1217.         $title = '=' x 77;
  1218.     }
  1219.     print $title, "\n";
  1220.     print HexDump( $data );
  1221.     print '=' x 77, "\n";
  1222. }

  1223. sub todo {
  1224.     my $self = shift;
  1225.     my ($id, $data) = @_;
  1226.     open FH, ">>todo.txt";
  1227.     select FH;
  1228.     $self->logmsg( sprintf( "接收到未知 [0x%02X:%d] 报文。", $id, $id ) );
  1229.     $self->pkgDump( $data, 'From ZS:' ) if ( defined $data and $data ne '' );
  1230.     select STDOUT;
  1231.     close FH;
  1232. }

  1233. sub getTimeStr(;$) {
  1234.     my $arg1 = shift;
  1235.     my @ret;

  1236.     if ( $arg1 ){
  1237.         @ret = localtime($arg1);
  1238.     } else {
  1239.         @ret = localtime;
  1240.     }

  1241.     $ret[4] += 1;
  1242.     $ret[5] += 1900;

  1243.     return sprintf( "%02d-%02d %02d:%02d:%02d", @ret[4, 3, 2, 1, 0] );
  1244. }

  1245. 1;
复制代码

[ 本帖最后由 flw 于 2007-3-13 18:31 编辑 ]

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
4 [报告]
发表于 2007-03-13 18:25 |只看该作者
另外还有一个解压模块,一个解密模块,都是用 C 写的,就不发了。

论坛徽章:
0
5 [报告]
发表于 2007-03-13 18:35 |只看该作者
噫,看上很不错啊。看来以后要看一下POE了, 这样来写client蛮好的。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
6 [报告]
发表于 2007-03-13 18:39 |只看该作者
原帖由 路小佳 于 2007-3-13 18:35 发表
噫,看上很不错啊。看来以后要看一下POE了, 这样来写client蛮好的。

不光是写 client 好,或者是写 server 好。
POE 适用于任何可以用有穷自动机算法描述的应用。
典型的比如计算器。

论坛徽章:
0
7 [报告]
发表于 2007-03-13 18:39 |只看该作者
封包格式和加解密算法自己分析出来的吗?

原来如此,呵呵,没事了

[ 本帖最后由 Namelessxp 于 2007-3-13 18:47 编辑 ]

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
8 [报告]
发表于 2007-03-13 18:42 |只看该作者
原帖由 Namelessxp 于 2007-3-13 18:39 发表
封包格式和加解密算法自己分析出来的吗?

决战的服务端源代码网络上有的,封包格式看源代码就可以看出来。
加密解密算法,还有压缩解压算法,没有源代码。

论坛徽章:
0
9 [报告]
发表于 2007-03-13 21:32 |只看该作者
哈哈,小case
flw你不是对RO也有研究吗
哈哈

评分

参与人数 1可用积分 -5 信誉积分 -5 收起 理由
flw -5 -5 恶意灌水

查看全部评分

论坛徽章:
0
10 [报告]
发表于 2007-03-13 21:35 |只看该作者
支持一下POE,偶还木研究过。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP